Skip to content

fix(engine.io-client): send the close packet when closing mid-handshake - #5575

Open
Jeremiah-D wants to merge 1 commit into
socketio:mainfrom
Jeremiah-D:fix/engine-io-close-packet-5274
Open

Jeremiah-D wants to merge 1 commit into
socketio:mainfrom
Jeremiah-D:fix/engine-io-close-packet-5274

Conversation

@Jeremiah-D

Copy link
Copy Markdown

Fixes #5274 (bug 2).

Hey — I dug into the second bug from #5274 on current master and could reproduce it: closing a polling transport mid-handshake never sends the close packet. Transport.close() calls onClose() synchronously right after doClose(), so the deferred once("open", ...) in Polling.doClose() can never fire — by the time the handshake response arrives the transport is already closed and the open packet is ignored.

Two changes:

  • Transport.close() now skips the synchronous onClose() when doClose() deferred the close (new closeDeferred flag); the transport calls onClose() itself once the deferred close runs.
  • Polling.doClose() no longer relies on a one-off "open" listener for the deferred close — Socket._onClose() clears all transport listeners on close, which would silently drop it. Instead the deferred close is picked up in onData when the handshake's open packet arrives, and the sid is read from the open packet itself since the Socket may already have dropped its listeners.

I also checked bug 1 from the issue while I was there: it does not reproduce on the JS side. flush() bails when transport.writable is false, and both the polling and websocket write() flip that flag synchronously, so back-to-back sendPacket calls just queue up until the next drain. Looks specific to the Java port's async EventThread dispatch.

Verified with two new tests in test/transport.js ("close during handshake"): stubbed fetch, handshake GET held back, close mid-handshake, then release. Both fail without the fix (no close packet POSTed) and pass with it — exactly one 1 packet POSTed with the sid in the URL, and no further polling afterwards. Full test/transport.js: 25 passing; the only 2 failures are pre-existing perMessageDeflate timeouts that fail identically on unmodified master in this environment.

If close() is called on a polling transport while the handshake is still
in flight, doClose() defers writing the close packet until the transport
opens, but Transport#close() then calls onClose() synchronously, flipping
the transport to "closed" so the deferred write never happens.

Defer onClose() along with the close packet instead, and drive the
deferred write off the handshake completion in onData rather than a
one-off "open" listener (the Socket clears all transport listeners when it
closes, which would silently drop that listener). The sid is read from the
open packet itself, so the close packet stays routable even when the
Socket has already dropped its listeners.
Copilot AI balanced review requested due to automatic review settings October 14, 2026 09:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Jeremiah-D
Jeremiah-D changed the base branch from master to main October 14, 2026 09:38
@Jeremiah-D Jeremiah-D closed this Oct 14, 2026
@Jeremiah-D Jeremiah-D reopened this Oct 14, 2026
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.

Code logic bugs in engine.io-client

2 participants