Skip to content

fix(net): reconnect when the session closes, not just when it errors#2183

Open
fperex wants to merge 1 commit into
moq-dev:devfrom
fperex:split/net-reconnect-close
Open

fix(net): reconnect when the session closes, not just when it errors#2183
fperex wants to merge 1 commit into
moq-dev:devfrom
fperex:split/net-reconnect-close

Conversation

@fperex

@fperex fperex commented Jul 12, 2026

Copy link
Copy Markdown

Split out of #2163.

A dropped session never reconnected. Established.closed resolves on every session end: qmux never rejects it, and native WebTransport rejects only on an abnormal close. But Reload only retried from its catch block, so a session that simply closed fell through without scheduling a retry, leaving a dead established, a status still reading "connected", throughput at zero, and no reconnect attempt ever. A page reload was the only way back.

This disproportionately hit Safari and Firefox, which ride the qmux WebSocket (where closed always resolves), so for them it was the only path to drop recovery.

The fix treats a resolved closed as a disconnect: clear established, report "disconnected", and retry on the existing backoff ladder.

Two subtleties, both deliberate:

  • The effect teardown and the session close both resolve to undefined, so racing them is ambiguous. A CANCELLED sentinel tells "the effect was torn down" apart from "the session ended", and only the latter reconnects.
  • The backoff ladder only resets once a session has survived STABLE_CONNECTION_MS. A relay that accepts us and immediately drops us (bad auth, shedding load, a redirect it cannot serve) would otherwise reset the ladder on every attempt and get hammered once a second forever.

Also pins the per-run cancel/abort handles, since a rerun swaps both before awaiting the spawn, and swallows the closed rejection in the bandwidth poll, which only uses it as a stop signal (that rejection is what drives the reconnect and must not surface as an unhandled rejection).

Public API changes: none.

Test plan: just js check green. Verified in the demo by bouncing the relay: the viewer reconnects on the backoff ladder instead of sitting on a permanently "connected" dead session.

Established.closed RESOLVES on every session end: qmux never rejects it and
WebTransport only rejects on an abnormal close. Reload only retried from its
catch block, so a session that simply dropped left a dead established, a
status stuck on connected, and no reconnect attempt. Firefox and Safari ride
the qmux WebSocket, so this was their only path to drop recovery and a page
reload was the only way back.

Race the session close against the effect teardown with a CANCELLED sentinel
(both resolve to undefined otherwise), then clear established, report
disconnected and retry on the existing backoff ladder. The ladder only resets
once a session survives STABLE_CONNECTION_MS, so a relay that accepts and
immediately drops us is not hammered once a second forever.

Also pin the per-run cancel/abort handles (a rerun swaps both before awaiting
the spawn) and swallow the closed rejection in the bandwidth poll, which only
uses it as a stop signal.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @fperex, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant