fix(websocket): remove WebSocketStream handshake abort listener after the handshake - #5713
Open
pacocartones wants to merge 1 commit into
Open
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5713 +/- ##
==========================================
- Coverage 93.47% 93.46% -0.01%
==========================================
Files 110 110
Lines 38906 38931 +25
==========================================
+ Hits 36366 36388 +22
- Misses 2540 2543 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
A WebSocketStream created with a signal added an abort listener during the opening handshake but never removed it. After the connection was established (or closed) the listener became a no-op yet stayed attached to the signal, so a long-lived signal kept every closed WebSocketStream and its listener closure alive, leaking memory and eventually triggering a MaxListenersExceededWarning when a signal is reused across streams. Remove the listener once the handshake concludes (on established, on socket close, and after the abort steps run). Assisted-by: Claude Opus 4.8 Signed-off-by: Paco Cartones <pacocartones@users.noreply.github.com>
pacocartones
force-pushed
the
fix/websocketstream-abort-listener-leak
branch
from
August 28, 2026 15:23
7e61b7a to
cd86efd
Compare
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.
What
WebSocketStreamattaches an abort listener to the caller'sAbortSignalduring the opening handshake (addAbortListeneratlib/web/websocket/stream/websocketstream.js:136) but never removes it.Why
Once the connection is established the listener becomes a no-op (it checks
isEstablishedand does nothing), yet it stays attached to the signal. Because the listener closure captures theWebSocketStreaminstance, a long-lived signal — for example an app-level shutdownAbortControllerreused across streams — keeps every already-closedWebSocketStreamalive. That is a memory leak, and it surfaces as aMaxListenersExceededWarningonce more than 10 streams share one signal.How
Store the disposable returned by
addAbortListenerand remove it once the handshake concludes: on#onConnectionEstablished, on#onSocketClose, and after the abort steps run. The removal helper is idempotent.Test
Adds
test/websocket/stream/abort-listener-leak.js, asserting the signal has zeroabortlisteners after both a clean close and an aborted handshake. It fails before the change (1 !== 0) and passes after. The existingtest/websocket/stream/*suite stays green (6 passing); lint clean.Reachability
new WebSocketStream(url, { signal })is ordinary use of a public API. Any consumer that reuses a signal across streams triggers the leak.