Network Type Transition Optimization

Nucleic-Session: 40F8C53A-F001-423A-8607-EAE181DBE14B
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-04 23:39:08 -07:00
co-authored by Nucleic
parent 63a1c9dd9e
commit 030d814ffe
@@ -28,7 +28,15 @@ final class NWFrameChannel: FrameChannel, @unchecked Sendable {
} }
init(endpoint: NWEndpoint) { init(endpoint: NWEndpoint) {
let params = NWParameters.tcp // Short TCP keepalive so a silently-dead LAN link (Wi-Fi dropped, Mac left the subnet) is
// detected in ~15s instead of the OS default of minutes — the belt to the path-monitor's
// braces for the case where a path still exists but the route to the Mac is gone.
let tcp = NWProtocolTCP.Options()
tcp.enableKeepalive = true
tcp.keepaliveIdle = 5
tcp.keepaliveInterval = 5
tcp.keepaliveCount = 2
let params = NWParameters(tls: nil, tcp: tcp)
params.includePeerToPeer = true params.includePeerToPeer = true
connection = NWConnection(to: endpoint, using: params) connection = NWConnection(to: endpoint, using: params)
peerDescription = "\(endpoint)" peerDescription = "\(endpoint)"
@@ -39,6 +47,13 @@ final class NWFrameChannel: FrameChannel, @unchecked Sendable {
connection.stateUpdateHandler = { [weak self] state in connection.stateUpdateHandler = { [weak self] state in
switch state { switch state {
case .ready: self?.markReady(); self?.onReady?() case .ready: self?.markReady(); self?.onReady?()
case .waiting(let error):
// No usable path right now (Wi-Fi dropped, host unreachable). NWConnection would sit
// in `.waiting` indefinitely, holding the transport hostage — the core of the "still
// on LAN after leaving Wi-Fi" stall. Fail fast so the store falls straight through to
// tailnet/relay. (Healthy connections go setup→preparing→ready and never hit this;
// the connect-timeout guard covers a wrong-network socket that stays `.preparing`.)
self?.onFailed?("waiting: \(error)"); self?.continuation.finish()
case .failed(let error): self?.onFailed?("\(error)"); self?.continuation.finish() case .failed(let error): self?.onFailed?("\(error)"); self?.continuation.finish()
case .cancelled: self?.continuation.finish() case .cancelled: self?.continuation.finish()
default: break default: break