nodejs / nodejs/undici

WebSocket transitions back from CLOSED to CLOSING

Open
#4,742 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug websocket
Dominant language
JavaScript
Stars
7.7k
Forks
879
Avg merge
2d 16h
Merged PRs (30d)
68

Description

Bug Description

When calling close() on a CONNECTING WebSocket, the readyState property is 3 (CLOSED) during the close event handler, but incorrectly reverts to 2 (CLOSING) after the handler returns. Once a WebSocket reaches the CLOSED state, it should never transition back to CLOSING.

Reproducible By

const { WebSocket } = require('undici');

const ws = new WebSocket('wss://echo.websocket.events/');

let capturedReadyState;

ws.addEventListener('close', () => {
  capturedReadyState = ws.readyState;
  console.log('during close event, readyState =', ws.readyState);
});

ws.close();

console.log('after close(), readyState =', ws.readyState);

setTimeout(() => {
  console.log('captured readyState from inside handler =', capturedReadyState);
  console.log('after 10ms, readyState =', ws.readyState);
}, 10);

setTimeout(() => {
  console.log('after 100ms, readyState =', ws.readyState);

  // Expected: readyState should be 3 (CLOSED) after close event
  // Bug: readyState is 2 (CLOSING) after the event handler returns
  if (ws.readyState === 3) {
    console.log('PASS: readyState is CLOSED (3)');
  } else {
    console.log('FAIL: readyState is', ws.readyState, '(expected 3)');
  }
  process.exit(0);
}, 100);

setTimeout(() => {
  console.log('Timeout - something went wrong');
  process.exit(1);
}, 5000);

Expected Behavior

after close(), readyState = 2
captured readyState from inside handler = undefined
after 10ms, readyState = 2
during close event, readyState = 3
after 100ms, readyState = 3
PASS: readyState is CLOSED (3)

See: https://jsbin.com/vugosuzeqa/edit?html,js,output

This follows from how the spec synchronously changes the state to CLOSING in https://websockets.spec.whatwg.org/#dom-websocket-close , and then after closing, queues a task to change the state to CLOSED and fire events.

So, this is probably the same root cause as #4741.

Relevant WPTs:

Logs & Screenshots

Actual behavior:

during close event, readyState = 3
after close(), readyState = 2
captured readyState from inside handler = 3
after 10ms, readyState = 2
after 100ms, readyState = 2
FAIL: readyState is 2 (expected 3)

The misordering is again related to #4741.

Environment

  • Node v25.2.1
  • undici 7.18.2

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the provided WebSocket reproducer and compare its behavior with the referenced WPTs: websockets/interfaces/WebSocket/close/close-connecting.html and close-connecting-async.any.js. Inspect the WebSocket close handling for the CONNECTING case; done means readyState remains CLOSED after the close event and the relevant WPT behavior passes.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
networking
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.