fix(relay): emit BUZZ_SYNC_REQUIRED gap signal on fan-out backpressure drops - #6090
fix(relay): emit BUZZ_SYNC_REQUIRED gap signal on fan-out backpressure drops#6090mfethe1 wants to merge 1 commit into
Conversation
…re drops
When a subscriber's outbound data channel is full, EVENT fan-out frames
are dropped silently: the socket stays up, the grace counter increments
server-side only, and the client's live view keeps a permanent hole
until the grace limit kills the connection (or a reconnect replay
accidentally heals it). Lane 3 of the sync-lag diagnosis proved this is
the first broken boundary behind "messages arrive late until restart".
Give the client a machine-readable gap signal instead:
- RelayMessage::sync_required() formats the extension frame
["BUZZ_SYNC_REQUIRED","backpressure"]. The constructor is deliberately
monomorphic so the relay cannot emit a frame the wire contract does
not define; protocol.rs module docs define the contract and delivery
rules (unknown relay->client heads are non-fatal per NIP-01 client
convention).
- The EVENT fan-out path — ConnectionManager::send_fanout_frame,
renamed from send_to_text_bytes to make its role explicit — routes
through try_send_fanout_frame:
* every dropped frame (full data channel, closed data channel, or
connection already gone) increments buzz_fanout_dropped_frames_total
with no sampling, so a silently lost EVENT always leaves a
telemetry trail;
* on a Full data channel with a live socket, the gap signal is queued
on the connection's priority control channel — never on the data
channel it signals about, never as a human-readable NOTICE — and
the existing consecutive-full grace counter decides cancellation
exactly as before;
* when the control channel is itself full or closed, in-band
signaling is impossible: cancel at once and let reconnect replay
recover the missed events;
* cancellation is idempotent under concurrent drops: a
compare-exchange on ConnEntry::backpressure_disconnect_counted
picks exactly one of the parallel droppers to increment
buzz_ws_backpressure_disconnects_total (a runtime flood harness
showed the unguarded is_cancelled->cancel sequence inflated the
disconnect counter 46x for one connection).
- Non-fan-out sends (CLOSED notices etc.) share the try_data_send
success path but keep the old drop semantics — no gap signal, no
dropped-frame counting.
Tests: 12 new tests plus a wire-shape pin (exact bytes) in the protocol
table test — full/closed/gone/ctrl-full branches, grace-counter
interaction, the signal never riding the data channel, disconnect
idempotence (including an 8-thread concurrent-drop case), and an
end-to-end case through the real send_fanout_frames path.
Gates on this Windows host: cargo fmt --check clean; cargo clippy
-p buzz-relay --tests --all-features -- -D warnings clean; cargo test
-p buzz-relay --lib = 885 passed / 43 ignored, failing only on 3
pre-existing host failures that reproduce on the base commit f956e6f
untouched by this diff (2 bash-HMAC tests resolve `bash` to WSL on
this host; the redis-gated mesh_demo test returns 504 with local
Redis up).
Co-authored-by: Michael Feth <michael@jira-flow.com>
Signed-off-by: Michael Feth <michael@jira-flow.com>
|
Hi @themiguelamador — could you approve the pending CI workflow runs on this PR at head Context on why the CI run matters beyond the usual signal: both dev hosts available to us are Windows, where several workspace gates fail at base for platform reasons. We therefore verified with targeted crate gates at the exact head on two independent Windows hosts (full evidence table in the PR description): Per the repo's |
What
Relay-side fix for the sync-lag diagnosis finding that the first broken boundary in relay->client sync is silent fan-out frame drops under backpressure. When the priority ctrl channel's fan-out buffer is full, the relay now emits a gap signal to the affected connection instead of dropping frames invisibly, letting clients know their view is stale.
Contract (frozen)
["BUZZ_SYNC_REQUIRED","backpressure"]on the priority ctrl channel.TrySendError::Fullfrom the fan-out path). A closed ctrl channel cancels immediately; no signal into a closed channel.sync_required(reason)is monomorphic overSyncRequiredReason::Backpressure— the relay cannot emit a reason the contract does not define.buzz_fanout_dropped_frames_totalcounts every drop unsampled.once_disconnect_counted) so concurrent disconnect paths count once per connection (8-thread regression test).protocol.rsmodule docs beside theRelayMessagedefinitions.Diff
3 files / 736 ins / 27 del, all under
crates/buzz-relay/(handlers/event.rs,protocol.rs,state.rs). Single commit on currentmain(f956e6fe), no rebase.Verification at exact head 0048e96
Two independent Windows (MSVC) hosts, separate gate stacks:
cargo test -p buzz-relay --lib --no-fail-fastcargo fmt --all -- --checkcargo clippy -p buzz-relay --tests --all-features -- -D warningsAll failures are pre-existing host-shaped issues reproducing on the
f956e6febase:api::git::policy::tests::bash_hmac_matches_rust_hmacandbash_hmac_single_ref— WSL bash resolution.api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo— redis-gated.telemetry::tests::trace_context_lookup_does_not_enable_callsites— passes in isolation (-- --exact); the test exists unchanged at base (telemetry.rs:475) and this diff touches zero telemetry code, so the flake is scheduler-shaped (global tracing callsite registration leaking between parallel tests), not diff-shaped. A Host A rerun reproduced the same intermittent flake.Per the repo's
ci.ymlpath-filter semantics, acrates/**diff triggersrust-lint/unit-tests/backend-integrationon ubuntu — maintainer-approved CI runs on this PR are the supported-environment complementary evidence.Attribution: commits carry
Co-authored-by+Signed-off-byfor the human operator; no AI attribution trailers.Evidence (contract 2026-08-17, tier T2 protocol/relay)
Locked-head exception applies: paired-gate receipts are locked at this exact head SHA, so this evidence lives in the body rather than a
docs/pr-evidence/6090/commit (a new commit would move the head and invalidate the receipts).Before — problem reproduced at base
f956e6fe(read-only diagnosis, 2026-08-16):try_send_ws_message(state.rs:592-620) onTrySendError::FullDROPS the frame with a server-side-onlywarn!;drop_countis never signaled to the client. Below the grace limit (default 15, config.rs:566-580) the socket stays "connected" with permanent holes in its event stream.relayReconnectReplay.ts, mobilerelay_session.dart:261-262).RESEARCH/SYNC_LAG_DIAGNOSIS_LANE3_HANDOFF_2026_08_16.md,.scratch/sync-lag-probe/probe_long.log,exp2.log(agent workspace).After — fix verified at head
0048e96a0(git rev-parse HEADin the same shell as the runs):BUZZ_SEND_BUFFER=1, scratch DBbuzz_gaptest; raw tokio-tungstenite + NIP-42 auth, victim stalled 8s while publisher floods kind-1): victim received 5 buffered EVENTs, then 8×["BUZZ_SYNC_REQUIRED","backpressure"]as the ctrl channel saturated at capacity 8 — the exact path that used to drop silently now emits the contracted client-visible gap signal. Capture taken during development at pre-fix head3cf682b50; the delta to this head is the metrics-inflation fix below + commit trailers (no mechanism change to the signal path).protocol.rs) and the end-to-end test throughsend_fanout_frames(handlers/event.rs) — both run green in the two-host gate table above at this exact head.buzz_fanout_dropped_frames_total); the harness also caught drops-after-cancel re-incrementingbuzz_ws_backpressure_disconnects_total(observed 46 for one connection) — fixed viais_cancelled()idempotence guards with regression testrepeated_drops_after_cancel_do_not_inflate_disconnect_counter, in this head.End-to-end: the first broken boundary in relay→client sync (silent fan-out drops under backpressure) is no longer silent: the relay emits a contracted gap signal on the priority ctrl channel, letting clients know their view is stale without tearing down the socket. Client-side replay on the signal is deliberately OUT of this PR (frozen client contract; follow-up lane).
Verified-by: paired Bingo + Winnie gate at
0048e96a0(two independent hosts, table above); gate-call PASS event77628f7d(Bingo/Winnie paired authority, Michael tiebreak, Ernie concurrence 2026-08-17T00:37Z). Contract: agent-workspaceGUIDES/PR_EVIDENCE_CONTRACT.md.