Skip to content

VAPI-4023 fix(signaling): clean teardown on server-initiated close and stop leaking diagnostics log listeners - #22

Merged
stampercasey merged 1 commit into
mainfrom
VAPI-4023_fix-disconnect-on-server-close
Sep 24, 2026
Merged

stampercasey merged 1 commit into
mainfrom
VAPI-4023_fix-disconnect-on-server-close

Conversation

@stampercasey

Copy link
Copy Markdown
Contributor

Summary

Fixes two defects on the disconnect path, both hit on every server-initiated websocket close (for example, when an endpoint is deleted):

  1. Spurious errors on server-initiated close. Every time the server closes the socket, Signaling._disconnect logged a TypeError: Cannot read properties of undefined (reading 'close') and two Error sending diagnostics Error: socket not ready.
  2. DiagnosticsBatcher leaked its logger listener. shutdown() removed this.handleLogEvent.bind(this), which is a new function each time, so nothing was ever removed. Every batcher (one per client) stayed subscribed to the module-level logger and kept appending every later SDK log line (including full SDPs at debug level) to its array forever.

A third issue sits on the same path and is fixed here too: disconnect() did not cancel a reconnect that rpc-websockets had already scheduled (for example, after a 1001), so the client reconnected after being torn down.

Root cause

rpc-websockets clears socket and ready synchronously when the socket closes, but emits close on the next tick. By the time our close handler calls _disconnect(false), there is no socket left to send on or close. The existing unit tests mock rpc-websockets, which hides this ordering.

Changes

  • signaling.ts: a socketOpen getter based on the client's ready flag. leave and diagnostics are only sent when the socket is open, ws.close() is skipped when the socket is already gone, and any pending reconnect_timer_id is cleared on disconnect. The rest of the teardown (auto-reconnect off, listeners removed, ws = null, ping interval cleared) always runs.
  • diagnostics.ts: bind the log listener once and remove that same function in shutdown().

Reproduction (failing before the fix)

  • New src/v1/signaling.serverClose.test.ts runs the real rpc-websockets client against a local server:
    • Server closes with 1000: before the fix, logger.error is called 3 times (1 TypeError + 2 "socket not ready"), exactly matching production. After the fix: 0.
    • Server closes with 1001, then the app calls disconnect(): before the fix, the server sees 2 connections (orphaned reconnect). After the fix: 1.
  • diagnostics.test.ts: new test that removeListener gets the same function passed to on. It failed before the fix.

E2E verification (pv-brtc-monitors delete-endpoint against lab)

npm pack of this branch vs. an unfixed origin/main build, installed into both node/common and node/delete-endpoint:

reading 'close' TypeError Error sending diagnostics All assertions passed
Before (origin/main) 1 2 yes
After (this branch) 0 0 yes (0 error-level lines)

Both runs end in the known macOS @roamhq/wrtc native teardown crash (exit 139 before, 134 after), which happens after the monitor has passed.

Production evidence (sdkVersion 0.8.0, 24h)

2880 TypeErrors (100% of delete-endpoint monitor target runs) and 5760 diagnostics errors. Monitor Lambda memory grows about 3 to 5 MB per run in warm environments until the process crashes. The listener leak is a likely contributor.

Testing

  • npm test: 126 passed, prettier check passes
  • npm run build passes

🤖 Generated with Claude Code

…d stop leaking diagnostics log listeners

- Skip leave/diagnostics sends and ws.close() when rpc-websockets has already
  dropped the socket, which is always the case inside its close handler.
- Cancel an already-scheduled reconnect on disconnect().
- DiagnosticsBatcher now removes the same logger listener it registered, so
  shut-down batchers stop retaining every later SDK log line.
@stampercasey
stampercasey requested review from a team as code owners September 24, 2026 15:40
@bwappsec

bwappsec commented Sep 24, 2026 •

Copy link
Copy Markdown

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues
✅ Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@stampercasey
stampercasey merged commit c079470 into main Sep 24, 2026
5 checks passed
@stampercasey
stampercasey deleted the VAPI-4023_fix-disconnect-on-server-close branch September 24, 2026 17:52
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