Skip to content

Fix #4984: Clean up socket.acks on emitWithAck timeout to prevent memory leak#5461

Open
cobyfrombrooklyn-bot wants to merge 1 commit intosocketio:mainfrom
cobyfrombrooklyn-bot:fix-issue-4984
Open

Fix #4984: Clean up socket.acks on emitWithAck timeout to prevent memory leak#5461
cobyfrombrooklyn-bot wants to merge 1 commit intosocketio:mainfrom
cobyfrombrooklyn-bot:fix-issue-4984

Conversation

@cobyfrombrooklyn-bot
Copy link

Problem

When using emitWithAck with a timeout, if clients don't respond before the timeout fires, the ack callbacks stored in socket.acks are never cleaned up. Each timed-out broadcast leaves orphaned entries in the acks Map, causing unbounded memory growth.

As reported in #4984, with 500 clients and frequent broadcasts where clients don't acknowledge, memory usage grew to 1.5GB in 3 hours.

Root Cause

broadcastWithAck in the adapter stores an ack callback in socket.acks.set(packet.id, ack) for each matching socket. When a client responds, socket.onack() deletes the entry. But when the timeout fires, only the caller's ack callback is invoked with an error; the per-socket entries in socket.acks are never removed.

Fix

  1. Added removeAcks(packetId, opts) to the in-memory adapter. It iterates the same set of matching sockets (using apply()) and deletes the ack entry for the given packet ID.
  2. Modified the timeout handler in BroadcastOperator.emit() to call removeAcks() before invoking the error callback. Uses a feature check (typeof this.adapter.removeAcks === 'function') for backward compatibility with custom adapters.

Test

Added should clean up socket.acks after emitWithAck timeout (memory leak fix) in test/messaging-many.ts. The test:

  • Connects two clients; one responds, one intentionally doesn't (simulating timeout)
  • Calls emitWithAck with a 200ms timeout
  • After the timeout error, verifies that all server-side socket acks maps are empty

The test fails without the fix (acks map retains the orphaned entry) and passes with it.

Tested locally on macOS ARM (Apple Silicon). All existing ack-related tests continue to pass.

When using emitWithAck with a timeout, if clients don't respond before the
timeout fires, the ack callbacks stored in socket.acks are never cleaned up.
Over time this causes unbounded memory growth proportional to the number of
timed-out broadcasts.

This adds a removeAcks() method to the in-memory adapter that iterates
matching sockets and deletes the orphaned ack entry. The broadcast operator
calls it when the timeout fires, before invoking the error callback.

Fixes socketio#4984
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.

1 participant