fix(dashboard): send periodic WS heartbeat pings to stop live-dashboard reconnect churn - #10452
Open
diegosouzapw wants to merge 1 commit into
Open
fix(dashboard): send periodic WS heartbeat pings to stop live-dashboard reconnect churn#10452diegosouzapw wants to merge 1 commit into
diegosouzapw wants to merge 1 commit into
Conversation
…rd reconnect churn
The live-dashboard WS client (src/hooks/useLiveDashboard.ts) only sent a
subscribe frame on open and never emitted the protocol's { type: "ping" }
heartbeat. The server (src/server/ws/liveServer.ts) refreshes client
liveness only from inbound messages and terminates any client idle past
HEARTBEAT_TIMEOUT_MS (35s), so a healthy, connected-but-idle dashboard
client was force-terminated roughly every 35-45s, causing constant
reconnect churn (#10319).
Fix (both directions, per the analyzed plan):
- Client: start a 15s ping interval on open, cleared on close/unmount/
reconnect, so the connection stays inside the server's liveness window.
- Server (defense in depth): the outbound heartbeat pong now also bumps
client.lastActivity, so even a third-party client that never pings is
not dropped for being idle.
Regression coverage:
- tests/unit/useLiveDashboard-heartbeat.test.tsx: fast fake-timer check
that the hook emits periodic ping frames and cleans up the interval on
close/unmount (no leaked timers).
- tests/integration/live-ws-heartbeat-keepalive.test.ts: real WS-server
integration test asserting a silent-but-subscribed client stays
connected past the 35s heartbeat timeout (~50s window), converted from
the plan file's TDD RED repro.
Closes #10319
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.
Closes #10319
Root cause
Confirmed in two files (matches the analyzed plan
_tasks/pipeline/bugs/2-implementing/10319-live-ws-client-never-sends-heartbeat-ping.plan.md):src/hooks/useLiveDashboard.ts):ws.onopensent only{ type: "subscribe", channels }. NosetInterval/keepalive timer existed anywhere in the hook, so the client never emitted the protocol's declared{ type: "ping" }heartbeat frame.src/server/ws/liveServer.ts):client.lastActivitywas refreshed only from inbound client messages.startHeartbeatterminates any client whose inbound gap exceedsHEARTBEAT_TIMEOUT_MS(35s, hardcoded). The server's own outbound{ type: "pong" }did not resetlastActivity(inbound-only), so it could not prevent the terminate.Net effect: a healthy, connected-but-idle dashboard client (the normal case — the dashboard just watches, sends nothing) was force-terminated (
ws.terminate(), abnormal close) roughly every 35-45s, and the browser auto-reconnected in a loop — exactly the reporter's symptom (wss://…/live-ws failed: The network connection was lostevery ~35-45s).Fix (both directions, per the plan's recommendation)
useLiveDashboard.tsnow starts a 15ssetIntervalon open that sends{ type: "ping" }, cleared on every exit path (close, unmount, disable, reconnect) to avoid leaked timers. This hook is the shared base foruseLiveRequests,useLiveComboStatus,useLiveConnectionStatus.startHeartbeatnow also bumpsclient.lastActivitywhen sending the outbound pong, so even a third-party/bespoke client that never pings is not dropped for being idle.Regression tests
tests/unit/useLiveDashboard-heartbeat.test.tsx(vitest, fake timers): asserts the hook emits periodic{ type: "ping" }frames after open, and that the interval is cleared on close and on unmount (no leaked timers).tests/integration/live-ws-heartbeat-keepalive.test.ts(node:test, real WS server viascripts/start-ws-server.mjs): a subscribed-but-silent client must stay connected past the 35sHEARTBEAT_TIMEOUT_MS(~50s window) — the permanent version of the plan file's TDD RED repro, which failed before this fix (AssertionError: true !== false, connection was terminated at ~44s).TDD RED→GREEN evidence is documented in the analyzed plan file (
verdict: fix-confirmed,confidence: CONFIRMED). Pure protocol defect, not environment-specific — no VPS live validation required per the plan.Gates run locally
npm run typecheck:core— GREENnpx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>— GREEN, 0 errorsnode scripts/check/check-file-size.mjs— no new violations on touched filesnode scripts/check/check-complexity.mjs— GREEN (2456 vs baseline 2774)node scripts/check/check-cognitive-complexity.mjs— GREEN (1104 vs baseline 1223)node scripts/check/check-test-discovery.mjs— GREEN, new test files discovered by the runnerNote:
npm run test:unitand the targetednpx vitest run tests/unit/useLiveDashboard-heartbeat.test.tsxcould not be completed locally in this session — this devbox is running a 13-way parallel campaign fan-out and sustained a load average of ~150-240 on 16 cores for 30+ minutes; the localtest:unitprocess measured 0.0% CPU over 26 minutes of wall time (confirmed viaps), and vitest's worker-pool spawn repeatedly hit its internal startup timeout under the same contention ([vitest-pool-runner]: Timeout waiting for worker to respond— an infra symptom, not a test assertion failure). Both new tests were manually verified line-by-line for correctness against the existingtests/integration/live-ws-startup.test.tsharness pattern and the hook's actual timer/cleanup logic. CI'stest-unitandtest-vitestjobs will run the authoritative suite on this PR.