fix: signal graceful leave at the transport so a disconnect doesn't show "reconnecting"

Field-testing the reconnect fix surfaced a follow-on bug: clicking Disconnect
(or quitting) showed the peer as yellow "Reconnecting" on the other end instead
of leaving cleanly. Logs showed the remote saw NeighborDown first and the gossip
`Leave` arrived ~15s later (broadcast then router torn down ~450ms after, so
NeighborDown — now meaning "reconnecting" — beats the slow, swarm-routed Leave).

Fix: make graceful leave a prompt, reliable TRANSPORT signal instead of relying
on gossip. On leave/quit, IrohTransport::leave() explicitly closes each live
connection with a distinguished goodbye code (GOODBYE_CODE) and aborts all
supervisors (so none linger redialing the about-to-close endpoint). The remote's
supervisor inspects the close reason: an application close with our goodbye code
=> emit the new ConnEvent::Left (evict now); a timeout/reset/other code =>
transient drop, reconnect as before. core handles ConnEvent::Left exactly like a
PeerLeft (cancel grace timer, disconnect, drop jitter, UI remove). ActiveSession
now holds the transport and calls leave() before shutting the router down.

Tracks the live connection per peer in Shared.live_conns (inserted on link up,
removed on drop) so leave() can close them. The gossip Leave path stays as a
harmless backup.

New test dialer_reports_left_on_graceful_close: a close with the goodbye code
reports Left and does NOT reconnect (mirror of the code-0 transient-drop test).
clippy clean, 4 transport tests pass. NOT yet field-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 00:08:33 -04:00
co-authored by Claude Opus 4.8
parent be818467dd
commit ffe5b43d8d
4 changed files with 156 additions and 12 deletions
+6
View File
@@ -54,6 +54,12 @@ pub enum ConnEvent {
Connecting(EndpointId),
/// A live audio link is established and carrying datagrams.
Connected(EndpointId),
/// The peer closed its link *gracefully* (an explicit QUIC application close,
/// which only happens on an intentional leave/quit — a network drop yields a
/// timeout, not this). Distinct from a transient drop so the core can evict
/// the peer immediately instead of waiting out the reconnect grace window.
/// This is the prompt, reliable leave signal; the gossip `Leave` is too slow.
Left(EndpointId),
}
#[derive(Serialize, Deserialize, Clone, Debug)]