fix: keep reconnecting across a long outage instead of evicting on NeighborDown
Field test of the long-outage path failed: ~31s into a Wi-Fi drop the peer
vanished with no "Reconnecting..." indicator and never came back. Logs showed
the chain `Gossip NeighborDown -> Removed peer -> Transport: stopped
supervising peer`.
Root cause: a transient gossip NeighborDown was routed to RoomEvent::PeerLeft,
same as a graceful leave, so core called disconnect_peer -> supervisor.abort().
That aborted the very reconnect supervisor that was meant to redial -- before
its own reconnect loop (which re-emits Connecting and re-dials the retained
09acefd address with backoff) ever ran. The supervisor + retained-address fix
were effectively dead code in the field, which is also why the loopback tests
(they drive the supervisor directly) never caught it.
Fix: decouple a transient drop from a graceful leave.
- gossip.rs: NeighborDown now emits the new RoomEvent::PeerConnectionLost
instead of PeerLeft. A graceful GossipMessage::Leave still emits PeerLeft.
- core: on PeerConnectionLost, do NOT disconnect the peer. Keep its supervisor
alive (it redials the retained address and drives the yellow indicator) and
arm a per-peer reconnect grace timer (RECONNECT_GRACE = 45s, comfortably past
the ~30s QUIC idle timeout). The peer is evicted only if the link hasn't
recovered when the timer fires. A gossip rejoin (PeerJoined/PeerUpdated) or a
transport reconnect (ConnEvent::Connected) cancels the timer first; session
shutdown aborts all pending timers so none fire a stray eviction.
Builds clean, clippy clean, 3 transport tests pass. NOT yet field-verified --
re-test the long-outage path on a real call.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -32,8 +32,16 @@ pub struct PeerState {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RoomEvent {
|
||||
PeerJoined(EndpointId, PeerState),
|
||||
/// A peer left *gracefully* (it broadcast a `Leave`). Evict it immediately.
|
||||
PeerLeft(EndpointId),
|
||||
PeerUpdated(EndpointId, PeerState),
|
||||
/// A peer's gossip neighbour link dropped without a graceful `Leave` (network
|
||||
/// blip, crash, walked out of range). This is transient: the audio supervisor
|
||||
/// keeps redialing, so the core marks the peer "reconnecting" and only evicts
|
||||
/// it if the link hasn't recovered within the reconnect grace window. Distinct
|
||||
/// from `PeerLeft` precisely so a momentary `NeighborDown` can't tear down the
|
||||
/// reconnect path the way it used to.
|
||||
PeerConnectionLost(EndpointId),
|
||||
}
|
||||
|
||||
/// Transport-level link state for a peer, surfaced so the UI can show when a
|
||||
|
||||
Reference in New Issue
Block a user