Reconnection & takeover
Laptops sleep, wifi drops, and VPS processes restart. ngstone is built so the same public URL comes back after all three, usually in well under the time it takes the dead-peer detector to even notice something is wrong.
Instance identity#
Every agent process mints an InstanceID— a stable 16-byte random hex value — once, at startup. It survives reconnects on that process and dies with it; a restarted agent gets a brand new one. Every binding in the edge's registry records the holder's InstanceID, and that identity is what authorizes taking a binding back, not whether the old holder still looks alive.
The takeover rules#
When a Bindrequest names a subdomain that's already held, the edge resolves it with three rules, in order:
- 1
Same InstanceID — immediate takeover
The requester is the same agent process reconnecting past a half-open zombie connection. Takeover succeeds instantly, regardless of whether the zombie session still looks alive to the edge. - 2
Different InstanceID, dead holder — takeover
The previous holder's session has already been detected as dead. The new session takes the binding. - 3
Different InstanceID, live holder — rejected
The edge returnsBindErr{subdomain_taken}. Two separate agents shouldn't silently fight over a name — liveness is only consulted in this one case.
On any successful takeover the edge sends the old session Evicted{hostname, reason: "takeover"}, force-closes it, and rebinds atomically under the registry lock. The evicted agent updates its status line instead of continuing to advertise a URL it no longer owns.
Reconnect backoff#
On any session error the agent enters a reconnect loop using decorrelated-jitter backoff from 500 ms up to a 30 s cap. Every reconnect attempt sends Takeover: true. Combined with rule (a) above, that makes reconnecting after a network partition effectively instant — the agent doesn't sit around waiting out the 45 s dead-peer detection window before it can have its own name back.
Dead-peer detection#
ConnectionWriteTimeout: 45s.Why the write timeout isn't 10s
ConnectionWriteTimeoutin yamux is a whole-session kill switch guarding every data send, not a keepalive knob. Set to 10 s, a saturated uplink or flaky hotel wifi mid-download would trip it and kill the entire session — every tunnel, every in-flight request — presenting as "the tunnel randomly drops under load". At 45 s it stays a backstop for genuinely wedged connections while liveness comes from the pings instead.Both layers converge on roughly 45 seconds worst-case detection — but rule (a) above means the binding's own owner never has to wait on that window to reclaim its name.
Zombie bindings#
When a session dies, its bindings are released via a compare-and-delete, not a blind delete keyed on hostname: each hostname is only removed if the registry still shows the dying session as its owner. This is what keeps a dying session's eventual cleanup from deleting a binding that a newer session already took over — the operation is idempotent, since it can be triggered both from the session-death path and from a graceful drain.