Skip to content

Worker.removeAllListeners() without event parameter causes message buffering until worker termination #59887

@vovchisko

Description

@vovchisko

Version

v22.17.1

Platform

Windows 10 Home

Subsystem

worker_threads

What steps will reproduce the bug?

1 - Two scripts are needed in the same module, parent and worker.

// test-parent.js
import { Worker } from 'node:worker_threads'

const worker = new Worker('./test-worker.js')

console.log('Parent: Starting worker...')

worker.on('message', (msg) => {
  if (msg.type === 'READY') {
    console.log('Parent: Got READY message:', msg)

    console.log('Parent: Calling removeAllListeners...')
    worker.removeAllListeners()

    console.log('Parent: Adding permanent listener...')
    worker.on('message', (msg) => {
      console.log('Parent: Permanent listener got message:', msg)
    })

    console.log('Parent: Sending command to worker...')
    worker.postMessage({ type: 'COMMAND' })
  }
})

setTimeout(async () => {
  console.log('Parent: Exiting...')
  await worker.terminate()
  process.exit(0)
}, 10000)
// test-worker.js
import { parentPort } from 'node:worker_threads'

console.log('Worker: Starting...')

parentPort.postMessage({ type: 'READY', timestamp: Date.now() })
console.log('Worker: Sent READY message')

parentPort.on('message', (msg) => {
  console.log('Worker: Received message:', msg)

  if (msg.type === 'COMMAND') {
    setInterval(() => {
      const message = { timestamp: Date.now() }
      console.log('Worker: Sending message:', message)
      parentPort.postMessage(message)
    }, 2000)
  }
})

2 - Run parent

node .\test-parent.js

Logs show that after removeAllListeners and creating a new listener for messages, the parent can't see any events from the worker. However, after worker termination, all messages are fired at once.

Parent: Starting worker...
Worker: Starting...
Parent: Got READY message: { type: 'READY', timestamp: 1757855880514 }
Parent: Calling removeAllListeners...
Parent: Adding permanent listener...
Parent: Sending command to worker...
Worker: Sent READY message
Worker: Received message: { type: 'COMMAND' }
Worker: Sending message: { timestamp: 1757855882527 }
Worker: Sending message: { timestamp: 1757855884530 }
Worker: Sending message: { timestamp: 1757855886534 }
Worker: Sending message: { timestamp: 1757855888543 }
Parent: Exiting...
Parent: Permanent listener got message: { timestamp: 1757855882527 }
Parent: Permanent listener got message: { timestamp: 1757855884530 }
Parent: Permanent listener got message: { timestamp: 1757855886534 }
Parent: Permanent listener got message: { timestamp: 1757855888543 }

How often does it reproduce? Is there a required condition?

100% reproducible.

What is the expected behavior? Why is that the expected behavior?

New event handlers created after removeAllListeners should fire in time.

What do you see instead?

Events fired by the worker message only emit after the worker's termination.

Additional information

Worth mentioning that specifying the event name worker.removeAllListeners('message') fixes the issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions