Skip to content

WebSocket transitions back from CLOSED to CLOSING #4742

@domenic

Description

@domenic

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions