Skip to content

fix(ext/node): keep worker alive while holding a refed MessagePort - #34877

Merged
littledivy merged 1 commit into
mainfrom
orch/divybot-429
Jun 5, 2026
Merged

fix(ext/node): keep worker alive while holding a refed MessagePort#34877
littledivy merged 1 commit into
mainfrom
orch/divybot-429

Conversation

@divybot

@divybot divybot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

A node:worker_threads Worker that holds a "refed" transferable object such as a MessagePort must not be terminated on idle. This covers the use cases from #23169:

  • a worker pool using a dedicated MessagePort for IPC, with a minimum number of workers always kept alive, and
  • a worker that is set up, idle, and waiting for work.

Root cause

The runtime idle-termination check hasMessageEventListener() in runtime/js/99_main.js consults messagePort.refedMessagePortsCount to decide whether a node worker should stay alive:

return (event.listenerCount(globalThis, "message") > 0 &&
  !globalThis[messagePort.unrefParentPort]) ||
  messagePort.refedMessagePortsCount > 0;

That counter is a mutable module-level let in ext/web/13_message_port.js. When ext/web was converted to lazy-loaded IIFE scripts in #33760, the module's real ESM export { refedMessagePortsCount } (a live binding) became a plain property on the returned object literal, capturing a one-time snapshot of 0. The refed-port branch of the idle check was therefore dead, and a worker kept alive only by a refed MessagePort (with no active "message" listener op promise) was incorrectly terminated on idle.

Fix

Restore the live-binding semantics by exposing refedMessagePortsCount as a getter on the module's return object.

Tests

Adds two spec tests under tests/specs/node/worker_threads/:

  • refed_messageport_idle — a worker receives delayed work over a refed transferred MessagePort after an idle period.
  • refed_messageport_holder — a worker kept alive purely by a refed MessagePort it does not listen on. This isolates the fix: it fails on main (worker terminates on idle) and passes with the change.

Closes #23169

Closes denoland/divybot#429

A node:worker_threads Worker that holds a "refed" transferable object such
as a MessagePort (e.g. a worker pool's dedicated IPC port, or a worker that
is set up and idle waiting for work) must not be terminated on idle.

The runtime idle-termination check `hasMessageEventListener()` in
`runtime/js/99_main.js` consults `messagePort.refedMessagePortsCount` to
decide whether a node worker should stay alive. That counter is a mutable
module-level `let` in `ext/web/13_message_port.js`. When ext/web was
converted to lazy-loaded IIFE scripts in #33760, the module's real ESM
`export { refedMessagePortsCount }` (a live binding) became a plain property
on the returned object, which captured a one-time snapshot of `0`. As a
result the refed-port branch of the idle check was dead and a worker kept
alive only by a refed MessagePort (with no active "message" listener op) was
incorrectly terminated on idle.

Restore the live-binding behavior by exposing `refedMessagePortsCount` as a
getter.

Adds spec tests covering both a worker that receives delayed work over a
refed port after an idle period, and a worker kept alive purely by a refed
MessagePort it does not listen on.

Closes #23169

Co-Authored-By: Divy Srivastava <me@littledivy.com>
@littledivy
littledivy enabled auto-merge (squash) June 5, 2026 04:26
@littledivy
littledivy merged commit 9656486 into main Jun 5, 2026
136 checks passed
@littledivy
littledivy deleted the orch/divybot-429 branch June 5, 2026 04:45
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.

[node:worker_threads] Worker with "refed" transferable objects such as MessagePort, ArrayBuffer, ... shall not be terminated on idle

2 participants