Fix WSConnection reconnect stall: don't block the event loop during the reconnect delay#252
Open
kojira wants to merge 1 commit into
Open
Fix WSConnection reconnect stall: don't block the event loop during the reconnect delay#252kojira wants to merge 1 commit into
kojira wants to merge 1 commit into
Conversation
…he reconnect delay WSConnection::doConnect() slept the hub thread with std::this_thread::sleep_for() for the reconnect delay. That thread also runs the uv event loop, so the sleep stalls the loop's cached clock by `delay` ms. The subsequent hub.connect() then arms its connection-timeout timer against the stale clock, so the timer is already expired and fires immediately on the next loop tick — tearing the freshly-connected socket down before the WS handshake completes. The first connect uses doConnect(0) (no sleep) so it succeeds, but every reconnect after a disconnect wedges permanently: the client keeps retrying and failing forever (each attempt: "Attempting to connect" -> "Websocket connection error"), never exiting, so a supervisor never restarts it. This silently stalls stream/sync/router/mesh whenever a connection drops. Fix: defer the reconnect with a non-blocking uS::Timer instead of sleeping, so the loop clock stays accurate and the connection-timeout works as intended. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Background / how this was hit in production (multi-day silent ingest stall): #253 |
kojira
added a commit
to kojira/strfry
that referenced
this pull request
Jul 13, 2026
…he reconnect delay doConnect() slept the hub thread with std::this_thread::sleep_for() for the reconnect delay. That thread also runs the uv event loop, so the sleep stalls the loop's cached clock; the subsequent hub.connect() then arms its connection-timeout against the stale clock, so it fires immediately and tears down the freshly-connected socket before the WS handshake completes. The first connect (no delay) works, but every reconnect after a disconnect wedges forever. Defer the reconnect via a non-blocking uS::Timer instead of sleeping the loop. Submitted upstream as hoytech#252. Co-Authored-By: Claude Opus 4.8 (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.
Summary
strfry streamandstrfry sync— the clients built onWSConnection— cannot reconnect after their first disconnect. The client keeps retrying and failing forever (Attempting to connect→Websocket connection errorevery 5s) even when the remote is perfectly reachable, and never recovers without a process restart. Since the process never exits, a supervisor (systemd/launchdRestart=) never restarts it — so a brief blip becomes an indefinite stall. (strfry routeris not affected: it has its own cron-scheduled reconnect that does not block the loop.)Root cause
WSConnection::doConnect()implemented the reconnect delay withstd::this_thread::sleep_for():That thread also runs the uWS/uv event loop, so the sleep blocks the loop and stalls its cached clock by
delayms (default 5000). The followinghub.connect(..., 5000, ...)arms a 5000 ms connection-timeout timer whose deadline is computed against the now-stale clock — already expired in real time — so it fires on the very next loop tick andonEnds the freshly-TCP-connected socket before the WebSocket handshake completes.The first connection uses
doConnect(0)(no sleep → fresh clock) so it works; every reconnect (doConnect(reconnectDelayMilliseconds)) wedges.Fix
Defer the reconnect with a non-blocking
uS::Timeron the hub's event loop instead of sleeping the loop thread, so the loop clock stays accurate and the connection-timeout behaves as intended.The same principle is why
cmd_router.cppdoesn't have this bug: it never sleeps the loop thread — its reconnect delay comes from a separate cron timer that just posts an event to the loop (viahubTrigger), and the actualhub.connect()runs on the loop thread. This patch doesn't copy the router's cron/inbox machinery; a one-shotuS::Timeron the loop is enough forWSConnection.Reproduce (deterministic)
Connected.Attempting to connect/Websocket connection errorevery 5s forever and never reconnects, even though the relay is back and a freshly-started client connects to it instantly.After this patch: the client reconnects on its next attempt.
Extra confirmation
Lowering
reconnectDelayMillisecondsbelow the 5000 ms connection-timeout (e.g. 250) also makes reconnect succeed with the old code — confirming the delay-vs-timeout clock interaction is the cause.