fix(p2p): connection cleanup — 4 fixes for stale/dead connections
PRD 4: Disable IPv6 direct dial/accept temporarily. IPv6 QUIC
handshakes succeed but connections die immediately on datagram
send ("connection lost"). IPv4 candidates work reliably. IPv6
candidates still gathered but filtered at dial time.
PRD 1: Close losing transport after Phase 6 negotiation. The
non-selected transport now gets an explicit QUIC close frame
instead of silently dropping after 30s idle timeout. Prevents
phantom connections from polluting future accept() calls.
PRD 2: Harden accept loop with max 3 stale retries. Stale
connections are explicitly closed (conn.close) and counted.
After 3 stale connections, the accept loop aborts instead of
spinning until the race timeout.
PRD 3: Resource cleanup — close old IPv6 endpoint before
creating a new one in place_call/answer_call. Add Drop impl
to CallEngine so tasks are signalled to stop on ungraceful
shutdown.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1493,3 +1493,12 @@ impl CallEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CallEngine {
|
||||
fn drop(&mut self) {
|
||||
// Safety net: if stop() was never called (crash, app
|
||||
// backgrounding), signal tasks to exit so they don't
|
||||
// spin on a dropped transport.
|
||||
self.running.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,8 +544,21 @@ async fn connect(
|
||||
// it for participant authentication.
|
||||
is_direct_p2p_agreed = use_direct;
|
||||
if use_direct {
|
||||
// Close losing relay transport so the
|
||||
// relay sees a clean disconnect instead
|
||||
// of waiting 30s for idle timeout.
|
||||
if let Some(loser) = race_result.relay_transport.as_ref() {
|
||||
loser.connection().close(0u32.into(), b"not-selected");
|
||||
}
|
||||
race_result.direct_transport
|
||||
} else {
|
||||
// Close losing direct transport so the
|
||||
// peer's endpoint doesn't retain a
|
||||
// phantom connection that pollutes
|
||||
// future accept() calls.
|
||||
if let Some(loser) = race_result.direct_transport.as_ref() {
|
||||
loser.connection().close(0u32.into(), b"not-selected");
|
||||
}
|
||||
race_result.relay_transport
|
||||
}
|
||||
}
|
||||
@@ -1358,7 +1371,11 @@ async fn place_call(
|
||||
.map(|la| la.port())
|
||||
.unwrap_or(0);
|
||||
|
||||
// Phase 7: create IPv6 endpoint, trying same port as v4
|
||||
// Phase 7: create IPv6 endpoint, trying same port as v4.
|
||||
// Close any leftover from a previous call first.
|
||||
if let Some(old) = sig.ipv6_endpoint.take() {
|
||||
old.close(0u32.into(), b"new-call");
|
||||
}
|
||||
let (sc, _) = wzp_transport::server_config();
|
||||
let v6_ep = wzp_transport::create_ipv6_endpoint(v4_port, Some(sc)).ok();
|
||||
let v6_port = v6_ep.as_ref()
|
||||
@@ -1477,7 +1494,10 @@ async fn answer_call(
|
||||
.map(|la| la.port())
|
||||
.unwrap_or(0);
|
||||
|
||||
// Phase 7: create IPv6 endpoint
|
||||
// Phase 7: create IPv6 endpoint. Close leftover first.
|
||||
if let Some(old) = sig.ipv6_endpoint.take() {
|
||||
old.close(0u32.into(), b"new-call");
|
||||
}
|
||||
let (sc, _) = wzp_transport::server_config();
|
||||
let v6_ep = wzp_transport::create_ipv6_endpoint(v4_port, Some(sc)).ok();
|
||||
let v6_port = v6_ep.as_ref()
|
||||
|
||||
Reference in New Issue
Block a user