Skip to content

Commit 697bdb1

Browse files
divybotlittledivy
andauthored
fix: send BroadcastChannel messages before close (#34628)
Fixes a race where BroadcastChannel.postMessage deferred the cross-worker send and then skipped it if the channel was closed before the deferred callback ran. This made node:worker_threads BroadcastChannel examples that call close() immediately after postMessage() hang waiting for a message that was never sent. The send is now committed to the shared broadcast queue before postMessage() returns, while receiver dispatch remains asynchronous. Added a node:worker_threads regression spec for the reported repro. Closes denoland/divybot#362 Fixes #31134 Verification: - timeout 10s target/debug/deno run --allow-all --unstable-broadcast-channel /tmp/orchid-362-repro.mjs - env -u RUSTC_WRAPPER cargo test -p specs_tests specs::node::worker_threads::broadcast_channel -- --nocapture - target/debug/deno fmt --check tests/specs/node/worker_threads/__test__.jsonc tests/specs/node/worker_threads/broadcast_channel.mjs - git diff --check Note: ./tools/format.js --check on the touched files spawned dprint plugin processes that did not exit in this workspace after reporting the initial import formatting diff; the reported diff was applied, and the new spec files pass deno fmt --check. Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
1 parent d11c5d0 commit 697bdb1

3 files changed

Lines changed: 23 additions & 6 deletions

File tree

ext/web/01_broadcast_channel.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,10 @@ class BroadcastChannel extends EventTarget {
138138
// Send to other listeners in this VM.
139139
dispatch(this, this[_name], new Uint8Array(data));
140140

141-
// Send to listeners in other VMs.
142-
defer(() => {
143-
if (!this[_closed]) {
144-
op_broadcast_send(rid, this[_name], data);
145-
}
146-
});
141+
// Send to listeners in other VMs. This must happen before returning from
142+
// postMessage(), otherwise close() immediately after postMessage() can
143+
// cancel the deferred send before other workers observe the message.
144+
op_broadcast_send(rid, this[_name], data);
147145
}
148146

149147
[refBroadcastChannel](ref) {

tests/specs/node/worker_threads/__test__.jsonc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
"output": "message_port_unref.out",
3535
"exitCode": 0
3636
},
37+
"broadcast_channel": {
38+
"args": "run --quiet --allow-read --unstable-broadcast-channel broadcast_channel.mjs",
39+
"output": "hello from worker\n",
40+
"exitCode": 0
41+
},
3742
"parent_port_unref": {
3843
"args": "run --allow-env --allow-read message_port_unref.mjs",
3944
"envs": {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { BroadcastChannel, isMainThread, Worker } from "node:worker_threads";
2+
3+
const bc = new BroadcastChannel("hello");
4+
5+
if (isMainThread) {
6+
bc.onmessage = (event) => {
7+
console.log(event.data);
8+
bc.close();
9+
};
10+
new Worker(new URL(import.meta.url));
11+
} else {
12+
bc.postMessage("hello from worker");
13+
bc.close();
14+
}

0 commit comments

Comments
 (0)