Skip to content

[MM-69687] Fix orphaned session when peer fails during WS reconnect (v1) - #1253

Open
bgardner8008 wants to merge 3 commits into
mainfrom
MM-69687-reconnect-leave
Open

[MM-69687] Fix orphaned session when peer fails during WS reconnect (v1)#1253
bgardner8008 wants to merge 3 commits into
mainfrom
MM-69687-reconnect-leave

Conversation

@bgardner8008

Copy link
Copy Markdown
Contributor

Summary

  • Adds sendLeaveAndClose() to WebSocketClient that correctly handles all three readyState cases: OPEN (send leave + close immediately), CONNECTING/mid-reconnect (mark closed to stop further reconnects, queue once('open', …) to send leave as soon as the socket opens), CLOSING/CLOSED (just cleanup).
  • Fixes a premature this.ws = null in the ReconnectTimeout error handler in client.ts that caused disconnect() to silently skip the leave message.
  • Guards startPingInterval() against this.closed to prevent a zombie interval when close() is called synchronously from inside the once('open', …) callback.

Worst case without this fix: leave is never delivered and the server reaps the CallSession row via pong timeout (~87s). With the fix, teardown is near-immediate.

This is the v1/RTCD backport of the fix made in the v2/LiveKit codebase via PR #1250 (MM-69672).

Test plan

  • All existing WebSocketClient tests pass
  • 5 new sendLeaveAndClose unit tests covering OPEN, CONNECTING (queued leave delivered on open), CONNECTING (no zombie ping interval), CLOSED, CLOSING
  • Manual: start a call, trigger a network blip while connected, verify session is cleaned up promptly rather than after ~87s

…reconnect

In v1 (RTCD), if an RTC peer connection failed while the plugin WebSocket
was CONNECTING (mid-reconnect), the send('leave') in disconnect() was
silently dropped — WebSocketClient.send() no-ops when readyState != OPEN.
The session would then sit on the server until the pong timeout (~87s).

Add sendLeaveAndClose() to WebSocketClient: OPEN → send + close immediately;
CONNECTING → mark closed (stops further reconnects), queue once('open', ...)
to send leave as soon as the socket opens; CLOSING/CLOSED → just cleanup.

Also fix the ReconnectTimeout error handler in client.ts, which was nulling
this.ws before calling disconnect(), causing disconnect() to skip the leave
entirely (the if (this.ws) guard would be false).

Guard startPingInterval() against this.closed to prevent a zombie interval
when close() is called synchronously from inside the once('open', ...) cb.
@codecov-commenter

codecov-commenter commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.75000% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 28.05%. Comparing base (e4c1671) to head (4184431).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
webapp/src/client.ts 0.00% 3 Missing ⚠️
webapp/src/websocket.ts 84.61% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1253      +/-   ##
==========================================
+ Coverage   28.00%   28.05%   +0.05%     
==========================================
  Files         241      241              
  Lines       13854    13867      +13     
  Branches     1637     1641       +4     
==========================================
+ Hits         3880     3891      +11     
- Misses       9543     9544       +1     
- Partials      431      432       +1     
Files with missing lines Coverage Δ
webapp/src/websocket.ts 85.62% <84.61%> (-0.10%) ⬇️
webapp/src/client.ts 21.97% <0.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f429885-d6ef-429a-9a2d-2ea7bd08b02f

📥 Commits

Reviewing files that changed from the base of the PR and between b4432f8 and 4184431.

📒 Files selected for processing (1)
  • webapp/src/client.ts

📝 Walkthrough

Walkthrough

WebSocketClient now provides state-aware leave-and-close teardown, suppresses ping setup after closure, and is covered across WebSocket states. CallsClient uses this method during disconnect, ignores joins after closure, and retains its WebSocket reference for native errors.

Changes

WebSocket teardown

Layer / File(s) Summary
State-aware WebSocket shutdown
webapp/src/websocket.ts, webapp/src/websocket.test.ts
Adds sendLeaveAndClose() for open, connecting, closing, and closed sockets; prevents ping intervals after closure; adds coverage for each state.
CallsClient teardown integration
webapp/src/client.ts
Routes disconnect through sendLeaveAndClose(), ignores join handling after closure, and no longer clears ws for native WebSocket errors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CallsClient
  participant WebSocketClient
  participant WebSocket
  CallsClient->>WebSocketClient: sendLeaveAndClose()
  WebSocketClient->>WebSocket: send leave and close when OPEN
  WebSocketClient->>WebSocket: wait for open when CONNECTING
  WebSocket-->>WebSocketClient: open event
  WebSocketClient->>WebSocket: send leave and close
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main fix: preventing orphaned sessions during WebSocket reconnect failures.
Description check ✅ Passed The description accurately summarizes the WebSocket teardown fix, state handling, and test plan in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch MM-69687-reconnect-leave

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
webapp/src/websocket.test.ts (1)

546-559: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test doesn't exercise the code path it claims to guard.

This test never invokes mockWebSocket.onopen. startPingInterval() (the function the this.closed guard was added to) is only ever called from inside ws.onopen in websocket.ts — it's never reached via the hello message path used here. Since pingInterval starts as null and is never set in this test, the final assertion passes trivially regardless of whether the guard exists.

To actually validate the fix, the reconnect-onopen path needs to run (so startPingInterval() executes) interleaved with the synchronous close() triggered by the queued open listener — e.g. by simulating a reconnect (isReconnect = true) and invoking mockWebSocket.onopen!(new Event('open')) rather than only onmessage. As written, this test would still pass even if the if (this.closed) { return; } guard in startPingInterval() were removed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webapp/src/websocket.test.ts` around lines 546 - 559, Update the test to
exercise the reconnect onopen path that calls startPingInterval: configure the
client/mock as a reconnect, invoke mockWebSocket.onopen!(new Event('open'))
after triggering sendLeaveAndClose so the queued close runs synchronously, and
then assert pingInterval remains null. Remove the hello onmessage simulation,
ensuring the assertion fails if the closed guard in startPingInterval is
removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webapp/src/websocket.ts`:
- Around line 188-213: Guard the join handling path during teardown: update the
ws.on('join') handler to immediately ignore or close when this.closed indicates
disconnection, preventing late acknowledgements from rebuilding RTCPeer. Ensure
sendLeaveAndClose's queued leave remains effective even if an existing open
listener runs first, and do not rely solely on removing open listeners.

---

Nitpick comments:
In `@webapp/src/websocket.test.ts`:
- Around line 546-559: Update the test to exercise the reconnect onopen path
that calls startPingInterval: configure the client/mock as a reconnect, invoke
mockWebSocket.onopen!(new Event('open')) after triggering sendLeaveAndClose so
the queued close runs synchronously, and then assert pingInterval remains null.
Remove the hello onmessage simulation, ensuring the assertion fails if the
closed guard in startPingInterval is removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ad02ca30-d4de-4180-a6f0-f3567abfa8b3

📥 Commits

Reviewing files that changed from the base of the PR and between fc3f93d and 4bc5e95.

📒 Files selected for processing (3)
  • webapp/src/client.ts
  • webapp/src/websocket.test.ts
  • webapp/src/websocket.ts

Comment thread webapp/src/websocket.ts
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.

3 participants