Skip to content

rta: keep reconnecting until the Conn is closed - #42

Open
HashimTheArab wants to merge 3 commits into
df-mc:mainfrom
HashimTheArab:rta/reconnect-until-closed
Open

rta: keep reconnecting until the Conn is closed#42
HashimTheArab wants to merge 3 commits into
df-mc:mainfrom
HashimTheArab:rta/reconnect-until-closed

Conversation

@HashimTheArab

Copy link
Copy Markdown
Contributor

Why

When the RTA WebSocket drops, rta.Conn redials four times with 1/2/4/8-second backoff and then closes itself for good with max reconnect attempt (4) reached as the cause. That is about twenty seconds of patience. An Xbox Live outage lasts far longer, so every long-lived connection comes out of one dead: each later Subscribe returns the cached cause, and only building a new Conn recovers. A socket that keeps dropping mid-handshake hits the same wall after four resubscribe rounds. We hit exactly this during the Xbox Live incident on 2026-09-02: session broadcasters that had been running for days looped on the cached error every two minutes until they were restarted.

What changed

  • dialer.reconnect retries until the dial lands or the context is done, with capped exponential backoff: 1s doubling to a 60s cap, plus up to 50% jitter. The first failure logs at Error, later ones at Warn, so a long outage is not an Error stream.
  • runReconnect no longer closes the Conn on dial failure or after repeated interrupted resubscribes. An interrupted round backs off on the same schedule and tries again. The subscriptions stay owned by the reconnect throughout; if the Conn is closed underneath it, they are deactivated with the close cause so nothing is left looking active on a dead connection. That includes a handshake that lands after Close has already run its own deactivation loop.
  • The backoff schedule is a package variable so tests can shorten it before dialing.

Unchanged: a reconnect with no live subscriptions still closes the socket normally, Close still ends everything, and Subscribe callers still bound their own wait with their context.

Tests

go test -race ./rta/ and go test ./... pass. New tests drive a server that refuses upgrades for ten attempts before accepting, a server that cuts the handshake eight times before letting it through, Close during an outage, and Close while a resubscribe handshake is mid-flight. The old "closes after persistent interrupted resubscribe" test is replaced by the outlasting version.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GMfvFUjXaF2P5m2gUmAJaT

A dropped RTA WebSocket got four redials about twenty seconds apart and
then the Conn closed itself with "max reconnect attempt reached" as the
cause. A service outage lasts longer than that, so every long-lived
Conn ended the outage dead: each later Subscribe returned the cached
cause and nothing short of building a new Conn recovered. The same
budget applied to a socket that kept dropping mid-handshake.

Reconnect now retries with capped exponential backoff (1s doubling to
60s, 50% jitter) until the dial lands or the Conn is closed, and an
interrupted resubscribe round backs off and goes again instead of
counting toward a limit. Subscriptions stay owned by the reconnect the
whole time; if the Conn closes underneath it they get the close cause.
Tests drive an outage through a server that refuses upgrades and a
handshake the server keeps cutting, and cover Close during an outage.

(cherry picked from commit 7ab4616)
…d Close

If Close ran while a resubscribe handshake was in flight, its deactivation
loop saw an empty map: the reconnect had taken the subscriptions, and the
successful handshake then tracked an active subscription back onto the
closed Conn with no HandleError. The reconnect now finishes that
deactivation when it sees the Conn closed.

Also: only the first failed redial logs at Error, later ones at Warn, so a
long outage is not an Error stream; and tests shorten the backoff through
a package variable set before Dial instead of writing to a live dialer.

(cherry picked from commit 6ebe74f)
A dial that completed just as Close swept c.conn could be stored after
the sweep, leaving an open socket behind a closed Conn. The reconnect now
publishes under connMu with a ctx check, so either Close sees the socket
or the reconnect sees the cancelled ctx and closes it itself.
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