relay: keep the reservation, not only acquire it - #3
Open
mkotelnikov wants to merge 1 commit into
Open
Conversation
libp2p re-dials a lost relay once and then never again: after the relay came back on the same port and key, no reservation reappeared within 45 s, while one explicit dialRelay restored it within a second (probe against libp2p 3.3.8 / circuit-relay-v2 4.2.11). The join keepalive watched the hub connection, which can stay up over WebRTC after the relay link is gone, so a relay restart left peers -- and hubs -- unreachable until reloaded. - superviseRelay (reservation.ts): detects the loss (relay connection:close, self:peer:update, a 10 s backstop), re-dials with jittered exponential backoff (1 s -> 30 s) and never gives up; poke() retries at once, stop() ends it. - watchPageWake (browser/page-wake.ts): online, visibilitychange to visible, pageshow and resume poke the supervisor, because a suspended tab's timers are throttled. - Wired into startHub, startBrowserHub and startBrowserPeer; unwound on a failed start and stopped first in stop(). - join.ts: the comment no longer claims the reservation is entirely libp2p-managed. Tests: a real relay that really restarts (tests/e2e/relay-supervisor), the backoff table, and the page-wake events. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
relay: keep the reservation, not only acquire it
A browser peer's (or hub's) WebSocket to the relay drops — tab suspended, laptop asleep, network switch, relay redeploy, proxy reload — and the reservation never comes back, so the peer stays unreachable until the page is reloaded. For a hub this takes down every member of its mesh.
Root cause (measured, not assumed)
libp2p re-dials a lost relay once, as the connection closes. A probe against a real relay (libp2p 3.3.8, circuit-relay-v2 4.2.11), restarted on the same port and key after a 2 s outage:
dialRelay: the reservation was back within about 1 s. So re-dialling is enough, and the node never needs rebuilding.The existing keepalive in
join.tswatches the hub connection. That can stay up over WebRTC after the relay link is gone, so the loss went unnoticed. The p2p-demo prototype had watched the relay; the watcher was pointed at the hub when it was ported to httpeers-stack.Changes (
apps/httpeers-stack)superviseRelay({ node, relayAddr })insrc/reservation.ts:connection:close, fromself:peer:update, and from a 10 s backstop check. Only a reservation on this relay counts.poke()retries at once;stop()ends everything.watchPageWake(onWake)insrc/browser/page-wake.ts:online,visibilitychange(to visible only),pageshowandresumecallpoke(). A suspended tab's timers are throttled, so the backoff alone could wait minutes after the page is usable again.startHubruns the supervisor.startBrowserHubandstartBrowserPeerrun it together withwatchPageWake.stop(), so a disconnected page stays disconnected.join.ts: the comment claiming the reservation is "entirely libp2p-managed" now says what is libp2p's job and what is ours.Tests
Each was watched failing before its implementation.
tests/e2e/relay-supervisor.test.ts, with a real relay that really restarts:stop()ends re-dialling;poke()retries despite a 60 s backoff;startHubgets its reservation back.tests/reservation.test.ts: a backoff table with hand-computed values. Removing the doubling, the cap or the jitter each fails it.tests/browser-page-wake.test.ts: the four events, hidden versus visible, and unsubscribe.stop()into a no-op, or dropping aremoveEventListener, fails the corresponding test.Gate
tsc --noEmitis clean, and Biome is clean on the changed files.a614eb4: the new suites plushubandhub-startuppass, 26/26.tests/e2e/browser.test.ts(2 in Chromium, 6 in Firefox) and are pre-existing: a cleanorigin/mainworktree fails the same 8 with identical messages.typecheck:testsalso fails onorigin/main, inbrowser-session.test.ts:8431536addedconnectionKindtoBrowserPeerHandlewithout updating the test's fake.Not in this PR
relay-lost/reconnectingsession phase; the page still showslivewhile restoring.stream_close_delay(deployment,statewalker/httpeers).This is step 1 of the agreed plan: supervisors, then a prototype where each hub relays signalling for its own members, with a TURN fallback so no application traffic crosses the relay or a hub.
🤖 Generated with Claude Code