fix(mesh): frame loss when recv_validated is cancelled mid-validation (#2458) - #2745
fix(mesh): frame loss when recv_validated is cancelled mid-validation (#2458)#2745brocoppler wants to merge 2 commits into
Conversation
…e silently lost (block#2458) run_demo_echo raced recv_validated against its 100ms drain tick in a select!. recv_validated reads a frame off the stream and then awaits Redis fence validation; a tick firing inside that await dropped the future and destroyed the frame it had already consumed. The stream then looked idle forever and the forwarding peer timed out — the block#2458 mesh_demo 504s, with loss probability ≈ validation latency / 100ms (worse under suite load, when Redis is slower). - reliable: split validate_received out of recv_validated so callers can extract frames cancel-safely and validate outside select!/timeout; document the cancel-safety contract. - mesh_boot: run_demo_echo selects only over frame extraction and validates outside the cancellable region. - buzz-relay-mesh: make IrohRecvHalf::recv_frame resumable (partial-read state lives in the half, not the future) so racing it is actually safe. - buzz-relay-mesh: loopback binds disable the iroh portmapper — a loopback socket can never use a WAN mapping, but the probe advertised one as a Portmapped candidate and path attempts to it fail with EADDRNOTAVAIL (regression test included). - mesh_demo: the forwarded-arm round trip runs over an in-memory stream pair by default (deterministic; reproduced the loss just like live transport) with the live-iroh variant kept as an #[ignore] evidence smoke. Verified: pre-fix the round trip failed ~40–80% locally (in-memory and live alike); post-fix 15/15 in-memory + 15/15 live + 3x full cargo test -p buzz-relay --lib green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Broc Oppler <brocoppler@gmail.com>
Signed-off-by: Broc Oppler <brocoppler@gmail.com>
4806ca4 to
eff239c
Compare
|
Fresh latest-main validation: CLEAR. I re-applied both PR commits ( Reproduction on unmodified current main, with Redis enabled:
The PR's cancellation contract is also supported by the currently locked Post-fix verification on Rust 1.95.0, isolated Postgres + Redis:
The original Broc Oppler author/DCO sign-off and Claude co-author attribution |
Summary
Root-causes the #2458 mesh_demo flake to a cancel-safety bug in production code and fixes it, plus one piece of endpoint hygiene the investigation surfaced.
The bug:
run_demo_echoracesrecv_validatedagainst its 100 ms drain tick in aselect!.recv_validatedreads a frame off the stream, then awaits Redis fence validation. When the tick fires inside that await, the future is dropped with the frame it already consumed — the stream then looks idle forever, the forwarding peer waits for an echo that can no longer happen, and the probe times out (the familiar 504). Loss probability per exchange ≈ validation latency ÷ 100 ms, which is why it worsens exactly when Redis is slow — i.e. under full-suite parallelism, matching #2458's "fails undercargo test, passes standalone" signature. Reproduced at 40–80 % locally, on an in-memory stream pair and live iroh endpoints alike, which rules the transport out as the cause.Changes
tunnel/reliable.rs— splitvalidate_receivedout ofrecv_validatedso callers can extract frames cancel-safely and validate outside anyselect!/timeoutregion; the cancel-safety contract is documented on both.mesh_boot.rs—run_demo_echonow selects only over frame extraction; decoding + fence validation run outside the cancellable region. Drain semantics unchanged.buzz-relay-mesh/peer.rs—IrohRecvHalf::recv_frameis now resumable: partial-read state lives in the half rather than the future (each underlyingRecvStream::readawait is itself cancel-safe), so racingrecv_framecannot desync the stream either.buzz-relay-mesh/endpoint.rs— loopback binds disable the iroh portmapper. A loopback-bound socket can never transmit to a WAN mapping, but the gateway probe (UPnP/PCP/NAT-PMP) advertised one as aPortmappedcandidate and path attempts to it fail (sendmsg→EADDRNOTAVAIL) — observed live during the investigation on a NAT-PMP-capable network. Production binds (0.0.0.0) are unaffected. Regression test included (trivially green on hosts without a portmap-capable gateway).api/mesh_demo.rs— the forwarded-arm round trip now runs over an in-memoryMeshStreampair by default: deterministic, fast, and it reproduced the frame loss identically, making it the regression test for the fix. The live-iroh variant is kept asdemo_join_forwarded_arm_round_trips_echo_live_meshunder#[ignore]for explicit transport evidence runs.Verification
--ignored)cargo test -p buzz-relay --libcargo clippy -p buzz-relay -p buzz-relay-mesh --lib --tests: clean.buzz-relay-meshsuite: 33 passed.Relation to existing work
Fixes #2458
🤖 Generated with Claude Code