feat: reconnect on transient link loss with per-peer supervisors
Audio links now survive a dropped QUIC connection instead of silently dying until the peer leaves and rejoins the room. Transport: each peer is owned by a single supervisor task running a connect → run → reconnect loop. The deterministic-initiator rule (lower id dials, higher accepts) holds on every reconnect, so one shared connection re-forms each time; the dialer redials with capped backoff and the acceptor awaits the inbound link, switching to a replacement immediately if one arrives before its own close fires. Inbound connections are routed to the supervisor via a per-peer channel. Fixes a latent bug from the prior design: aborting a peer's tasks left the detached send/read loops running, holding Connection clones so the link never actually closed. The loops now live in abort-on-drop guards scoped to the supervisor, so cancelling it releases the connection. UX: a new ConnEvent stream surfaces per-peer link state to the UI, which shows "Connecting…"/"Reconnecting…" with a yellow indicator and border while a peer's audio link is down, returning to normal when it recovers. Tests: the loopback test now runs through the supervisor path, plus a new test drives a real drop (explicit close of a controlled peer endpoint) and asserts the dialer re-dials the stable address and audio resumes over the rebuilt connection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,18 @@ pub enum RoomEvent {
|
||||
PeerUpdated(EndpointId, PeerState),
|
||||
}
|
||||
|
||||
/// Transport-level link state for a peer, surfaced so the UI can show when a
|
||||
/// peer's audio connection is being (re)established versus actually carrying
|
||||
/// audio. Distinct from `RoomEvent`: a peer can be present in the gossip room
|
||||
/// while its audio link is momentarily down and reconnecting.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConnEvent {
|
||||
/// No live audio link yet — initial connect or reconnecting after a drop.
|
||||
Connecting(EndpointId),
|
||||
/// A live audio link is established and carrying datagrams.
|
||||
Connected(EndpointId),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct PeerSpeakTicket {
|
||||
pub host_addr: iroh::EndpointAddr,
|
||||
@@ -83,6 +95,9 @@ pub trait NetworkTransport: Send + Sync {
|
||||
|
||||
/// Subscribes to incoming datagrams from any peer.
|
||||
async fn receive_datagrams(&self) -> Result<Receiver<(EndpointId, Bytes)>, NetError>;
|
||||
|
||||
/// Subscribes to per-peer link-state changes (connecting / connected).
|
||||
async fn subscribe_conn_events(&self) -> Result<Receiver<ConnEvent>, NetError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
Reference in New Issue
Block a user