Fix DeepgramBackend crashing the whole server on a bad-connection reconnect loop - #109
Fix DeepgramBackend crashing the whole server on a bad-connection reconnect loop#109bgrozev wants to merge 1 commit into
Conversation
…k overflow Found via the integration test harness with a deliberately bad API key: undici's WebSocket.close() can synchronously re-dispatch 'error'/'close' on a connection that never opened. DeepgramBackend's 'error' and 'close' listeners both call close(), which calls ws.close() again - without a reentrancy guard this recurses without bound and crashes the whole Node process with a stack overflow, not just the one connection with bad credentials. close() now checks/sets status = 'closed' before touching the WebSocket, mirroring the doClose() idempotency pattern already used by OutgoingConnection and TranslatorConnection elsewhere in this codebase. Added a test that reproduces the crash by making the mock WebSocket's close() synchronously re-fire 'error' (matching the real undici behavior) - it throws RangeError: Maximum call stack size exceeded before this fix, passes after. Also notes the pitfall in BACKENDS.md's backend-implementation checklist and CLAUDE.md's Deepgram notes, since it's a general trap for any backend whose error/close handlers call close() themselves.
Code ReviewOverviewThis PR fixes a legitimate process-crashing bug: What works well
Issue: resource leak in the server-initiated
|
Summary
DeepgramBackend.close()was not idempotent. Found via the new integration test harness (Add integration test harness (container/worker x opus-backend x provider) #108) when testing a deliberately invalidDEEPGRAM_API_KEY: undici'sWebSocket.close()can synchronously re-dispatch'error'/'close'on a connection that never opened, and sinceDeepgramBackend's own'error'/'close'listeners callclose(), this recurses without bound and crashes the entire Node process with a stack overflow — not just the one session with bad credentials, every other active participant too.close()now checks/setsstatus = 'closed'before touching the WebSocket, mirroring thedoClose()idempotency patternOutgoingConnection/TranslatorConnectionalready use elsewhere in this codebase.close()synchronously re-fire'error'(matching the real undici behavior observed in the crash trace) — it throwsRangeError: Maximum call stack size exceededbefore this fix, passes after.BACKENDS.md's backend-implementation checklist andCLAUDE.md's Deepgram notes, since any backend whose error/close handlers callclose()themselves is exposed to the same trap.