The hazard
The engine's Timeout implementation for subduction calls is NeverTimeout (engine/guest/src/lib.rs ~:307-318): it literally returns Ok(fut.await) — no bound, ever. It is ours, not upstream.
This was a co-conspirator in the #113 stale-transport hang (fixed in #122): sync_with_peer walks a peer's connection list serially, and a call parked on a dead-but-unfailable transport wedged the sync forever — where a real bound would have made it a 30s stall with a diagnosis. The queue-closing fix in #122 removes that specific corpse, but NeverTimeout still converts every other "slow or silent peer" failure mode into "wedged forever":
- a peer that is reachable but never answers (wedged remote engine, half-open path the transport layer never notices);
- any future state-management bug of the same family — the class is "the await never resolves", and the timeout is the generic defense the engine currently opts out of.
What to decide (a measured decision, not a quick patch)
Gate sketch
The headless rebind-sync-check.ts idiom (demo/host/, just rebind-sync) extends naturally: a peer that accepts the wire and then goes silent (blackhole its relay path via the e2e severable proxy, or SIGSTOP a headless peer process) must produce a bounded, named sync outcome instead of a parked handle.
Context: #113 (the latch), #122 (the fix that flagged this), demo/e2e/scenarios/relay-partition.ts's wave-3 banner (the serial-walk-under-uncapped-timeout link).
The hazard
The engine's
Timeoutimplementation for subduction calls isNeverTimeout(engine/guest/src/lib.rs ~:307-318): it literally returnsOk(fut.await)— no bound, ever. It is ours, not upstream.This was a co-conspirator in the #113 stale-transport hang (fixed in #122):
sync_with_peerwalks a peer's connection list serially, and a call parked on a dead-but-unfailable transport wedged the sync forever — where a real bound would have made it a 30s stall with a diagnosis. The queue-closing fix in #122 removes that specific corpse, butNeverTimeoutstill converts every other "slow or silent peer" failure mode into "wedged forever":What to decide (a measured decision, not a quick patch)
CallTimeout::Defaultplumbing already exists at the call sites (sync_with_peer(…, CallTimeout::Default)), so the change is choosing and wiring a real implementation, not new surface.conn-statusdistinguishes a dead wire (gone:) from a live one — a sync timeout against a wire the engine calls alive is its own honest diagnosis ("peer silent"), and should not be conflated with either transport death or handshake failure.gone:-gated re-dial deliberately does NOT treat other errors as re-dial triggers (Subduction sync delivers nothing when the writer dials: connected + ready, reader stuck at revision 0 #78 double-dial discipline). A timeout outcome would land in the keeper's "leave it alone / report" arm — verify that stays correct once timeouts exist.Gate sketch
The headless
rebind-sync-check.tsidiom (demo/host/,just rebind-sync) extends naturally: a peer that accepts the wire and then goes silent (blackhole its relay path via the e2e severable proxy, or SIGSTOP a headless peer process) must produce a bounded, named sync outcome instead of a parked handle.Context: #113 (the latch), #122 (the fix that flagged this), demo/e2e/scenarios/relay-partition.ts's wave-3 banner (the serial-walk-under-uncapped-timeout link).