fix: prevent memory leak from orphaned sockets on reconnect - #19
fix: prevent memory leak from orphaned sockets on reconnect#19FranzFelberer wants to merge 2 commits into
Conversation
On every non-logout disconnect, startWhatsAppConnection() recursively called itself, creating a new socket without ending the previous one or detaching its ev.process handler. Under a WhatsApp-side disconnect storm (timedOut closes -- I have seen bursts of thousands per minute in production), orphaned sockets accumulate, each retaining its WebSocket, keep-alive timer and signal-key cache, leaking memory until the process reaches several GB. Latent second bug: the recursive call returns a new socket while the caller (the MCP server) keeps the original reference, so sends target a dead socket after the first reconnect. Rework the connection into a single self-healing loop: - tear down the dying socket (detach the ev handler + sock.end()) before reconnecting, so its resources become collectable - single-flight guard + exponential backoff (1s..30s), reset on "open", so a storm can no longer spin up sockets unbounded - return a stable Proxy that always delegates to the live socket, so callers keep a valid reference across reconnects Behaviour is otherwise unchanged. In production this dropped a bridge that had grown to ~1.8 GB (10 days uptime) to ~80-130 MB. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesWhatsApp Connection Self-Healing Infrastructure
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src/whatsapp.ts`:
- Around line 126-147: The teardown function leaves currentSock pointing to the
closed socket so callers can still use the dead connection; modify teardown (the
teardown(dead: WhatsAppSocket) function) to clear the live-socket pointer (set
currentSock = null or equivalent) before ending the socket, ensuring the proxy
no longer exposes the dead instance during backoff; keep existing detach
handling and dead.end call and update any related state (if any) used by
scheduleReconnect/connect so reconnect logic remains unchanged.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: cc0a2f53-05d7-4027-bfae-12c7321b0010
📒 Files selected for processing (1)
src/whatsapp.ts
Addresses the CodeRabbit review on jlucaso1#19: during the reconnect backoff window, teardown() ended the socket but left currentSock pointing at it, so the Proxy still exposed the dead connection and a send attempted in that window targeted an already-ended socket. - teardown() now nulls currentSock (guarded by identity) before ending it - the Proxy reports undefined while disconnected, so sendWhatsAppMessage's `!sock.user` guard cleanly reports "not connected" during backoff - guard the Proxy set trap too, for symmetry - add a JSDoc to startWhatsAppConnection Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem
On every non-logout disconnect,
startWhatsAppConnection()recursively calls itself (src/whatsapp.ts):This builds a brand-new socket without ending the previous one or detaching its
ev.processhandler. Under a WhatsApp-side disconnect storm (timedOutclose events — I've observed bursts of thousands per minute), orphaned sockets pile up, each retaining its own WebSocket, keep-alive timer and signal-key cache. Memory grows unbounded — in my setup the process reached ~1.8 GB after ~10 days (peak ~2.9 GB).There is also a latent correctness bug: the recursive call returns a new socket, but the caller (the MCP server) keeps the original reference — so after the first reconnect,
sendMessagetargets a dead socket.Fix
Rework the connection into a single self-healing loop:
ev.processhandler (via the cleanup function it returns) and callsock.end()— so its resources become collectable.open), so a disconnect storm can no longer spin up sockets unbounded.Proxythat always delegates to the live socket, so callers keep a valid reference across reconnects.The public API (
startWhatsAppConnection,sendWhatsAppMessage) and behaviour are unchanged.Result
In production, a bridge that had grown to ~1.8 GB dropped to ~80–130 MB and stayed flat across reconnects.
One file changed (
src/whatsapp.ts);node --checkclean.Summary by CodeRabbit