VAPI-4023 fix(signaling): clean teardown on server-initiated close and stop leaking diagnostics log listeners - #22
Merged
Conversation
…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.
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
smoghe-bw
approved these changes
Sep 24, 2026
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
Fixes two defects on the disconnect path, both hit on every server-initiated websocket close (for example, when an endpoint is deleted):
Signaling._disconnectlogged aTypeError: Cannot read properties of undefined (reading 'close')and twoError sending diagnostics Error: socket not ready.DiagnosticsBatcherleaked its logger listener.shutdown()removedthis.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
socketandreadysynchronously when the socket closes, but emitscloseon the next tick. By the time ourclosehandler 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: asocketOpengetter based on the client'sreadyflag.leaveand diagnostics are only sent when the socket is open,ws.close()is skipped when the socket is already gone, and any pendingreconnect_timer_idis 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 inshutdown().Reproduction (failing before the fix)
src/v1/signaling.serverClose.test.tsruns the real rpc-websockets client against a local server:logger.erroris called 3 times (1 TypeError + 2 "socket not ready"), exactly matching production. After the fix: 0.disconnect(): before the fix, the server sees 2 connections (orphaned reconnect). After the fix: 1.diagnostics.test.ts: new test thatremoveListenergets the same function passed toon. It failed before the fix.E2E verification (pv-brtc-monitors
delete-endpointagainst lab)npm packof this branch vs. an unfixedorigin/mainbuild, installed into bothnode/commonandnode/delete-endpoint:reading 'close'TypeErrorError sending diagnosticsAll assertions passedBoth runs end in the known macOS
@roamhq/wrtcnative 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 passesnpm run buildpasses🤖 Generated with Claude Code