fix(websocket): queue close/error events asynchronously when closing during CONNECTING - #5078
fix(websocket): queue close/error events asynchronously when closing during CONNECTING#5078maruthang wants to merge 1 commit into
Conversation
When close() is invoked on a WebSocket in the CONNECTING state, undici synchronously dispatched the error and close events from inside close() via failWebsocketConnection -> handler.onSocketClose(). This violates the WHATWG WebSocket spec, which requires these events to be fired by queuing a task, and caused user code to observe events before close() returned. Defer the failure path in closeWebSocketConnection to a microtask: abort the underlying fetch immediately and set readyState to CLOSING, but run onSocketClose (which dispatches error and close) from a queued task. The microtask guards against re-entry (e.g. a second close() or a racing socket close) by bailing when readyState is already CLOSED. Other failWebsocketConnection call sites are unchanged. Refs nodejs#4741 Signed-off-by: Maruthan G <maruthang4@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5078 +/- ##
=======================================
Coverage 93.10% 93.11%
=======================================
Files 110 110
Lines 35807 35822 +15
=======================================
+ Hits 33339 33354 +15
Misses 2468 2468 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
KhafraDev
left a comment
There was a problem hiding this comment.
Please look at prior PRs before opening slop PRs and wasting our time reviewing. Jfc.
|
Apologies @KhafraDev — I hadn't seen #4745 or your comment there before opening this, and you're right that a localized microtask in Closing this in favor of going back and doing it properly. I'll study the existing work on #4745 first so I'm not duplicating effort or getting in your way. If a fresh PR would be useful at some point, I'll only open one after reading the thread and only if it's clearly a different / complementary direction — otherwise I'll stay out of it. Sorry for the review time. |
This relates to...
Fixes #4741
Rationale
Per the WHATWG WebSocket spec, when
close()is invoked on aWebSocketwhosereadyStateisCONNECTING, the implementation must "fail the WebSocket connection", and all resulting event dispatches (error,close) must be delivered via a queued task — not synchronously from theclose()call itself.Prior to this PR, undici dispatched those events synchronously from inside
close()viafailWebsocketConnection→handler.onSocketClose(). User code observederrorandcloseevents before theclose()method returned, which is observably different from browser behavior and breaks consumers that set listeners after callingclose().The fix is intentionally scoped to the
CONNECTINGbranch ofcloseWebSocketConnection. Other callers offailWebsocketConnection(receiver errors, handshake failures,WebSocketStream's abort listener) keep their current synchronous behavior, preserving existing tests such astest/websocket/stream/abort-before-open.js.Changes
lib/web/websocket/connection.js: IncloseWebSocketConnection, when the socket is stillCONNECTING, continue to abort the underlying fetch synchronously and setreadyState = CLOSING(so thatclose()returns with the correct observable state), but defer theonSocketClose()dispatch — which fireserrorandclose— to aqueueMicrotask. The microtask guards against re-entry (e.g. a racing socket close or a secondclose()call) by bailing whenreadyStateis alreadyCLOSED.test/websocket/issue-4741.js: New regression test asserting that noerrororcloseevent is dispatched synchronously fromclose()when the socket is inCONNECTING.Features
N/A
Bug Fixes
close/errorevents asynchronously incloseWebSocketConnectionwhen the socket is stillCONNECTING(WebSocket events fire synchronously during close() instead of asynchronously #4741).Breaking Changes and Deprecations
None.
Status