Skip to content

feat: add heartbeat to detect and clean up dead WS connections - #163

Open
Paranoa-dev wants to merge 1 commit into
stellar-vortex-protocol:mainfrom
Paranoa-dev:feature/ws-heartbeat
Open

feat: add heartbeat to detect and clean up dead WS connections#163
Paranoa-dev wants to merge 1 commit into
stellar-vortex-protocol:mainfrom
Paranoa-dev:feature/ws-heartbeat

Conversation

@Paranoa-dev

Copy link
Copy Markdown

Add heartbeat ping/pong to detect dead WebSocket connections

Summary

Adds a periodic heartbeat mechanism to IntentsGateway that detects and cleans up dead WebSocket connections (e.g., network drops) that would otherwise remain in the subscribers set indefinitely, silently wasting broadcast() cycles.

Problem

IntentsGateway only removes a subscriber on an explicit error or close event. A connection that goes dead without a clean close (e.g., network drop, mobile sleep, load balancer timeout) stays in the subscribers set indefinitely, causing:

  • Wasted broadcast() cycles sending to a socket that will never receive
  • Memory leaks from accumulated dead connections
  • Potential send errors that could disrupt other clients

Solution

Implemented the standard ws library heartbeat pattern:

How it works

  1. On connection: Each client is marked as alive = true and a pong listener is registered
  2. Every 30 seconds (HEARTBEAT_INTERVAL_MS):
    • For each subscriber, check if alive === false → if so, client.terminate() and remove from set
    • Otherwise, set alive = false and send ping
  3. On pong response: Client is marked alive = true again
  4. On module destroy: Heartbeat interval is cleared

Implementation details

  • Uses a WeakMap<WebSocket, boolean> to track alive status (avoids monkey-patching the WebSocket instance)
  • OnModuleDestroy interface ensures cleanup on application shutdown
  • getAliveCount() exposed for observability/monitoring
  • 30-second interval matches the ws library recommended pattern

Code changes

src/intents/intents.gateway.ts:

  • Added WeakMap<WebSocket, boolean> for alive tracking
  • Added setInterval-based heartbeat loop in constructor
  • Added pong event handler in handleConnection
  • Added heartbeat() private method with terminate logic
  • Added onModuleDestroy() for cleanup
  • Added getAliveCount() for observability

src/intents/intents.gateway.spec.ts (new):

  • Tests connection alive marking
  • Tests disconnect cleanup
  • Tests dead client termination (no pong response)
  • Tests alive client survival (pong received)
  • Tests interval cleanup on module destroy
  • Tests broadcast to all alive subscribers

Test Results

Unit tests:  24 passed (4 suites, including 6 new gateway heartbeat tests)
E2e tests:   23 passed (5 suites)
Total:       47 tests passing

Migration Notes

When scaling to multiple instances, the heartbeat pattern remains the same since each instance maintains its own WebSocket connections. No cross-instance coordination is needed.

Checklist

  • Periodic ping sent to all connected clients
  • Pong response marks client as alive
  • Dead clients (no pong within interval) are terminated
  • Terminated clients removed from subscriber set
  • Heartbeat interval cleaned up on module destroy
  • Unit tests for all heartbeat behaviors
  • npm run lint passes
  • npm run typecheck passes (pre-existing errors only)
  • npm test passes
  • npm run test:e2e passes

Closes #81

- Add periodic ping/pong heartbeat to IntentsGateway using the standard
  ws library pattern
- Track alive status per client with a WeakMap
- Every 30s, mark all clients as 'not alive', send ping; clients that
  respond with pong are marked alive again; those that don't are
  terminated and removed from the subscriber set
- Implement OnModuleDestroy to clean up the heartbeat interval
- Add unit tests for heartbeat behavior (alive marking, pong response,
  dead client termination, interval cleanup)
- Fix e2e config for pre-existing test environment issues

Closes stellar-vortex-protocol#81
@drips-wave

drips-wave Bot commented Jul 28, 2026

Copy link
Copy Markdown

@Paranoa-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

Add heartbeat ping/pong to detect dead WebSocket connections

1 participant