Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions src/js/node/worker_threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,23 +755,26 @@ function receiveMessageOnPort(port: MessagePort) {
// TODO: parent port emulation is not complete
function fakeParentPort() {
const fake = Object.create(MessagePort.prototype);
Object.defineProperty(fake, "onmessage", {
get() {
return self.onmessage;
},
set(value) {
self.onmessage = value;
},
});

Object.defineProperty(fake, "onmessageerror", {
get() {
return self.onmessageerror;
},
set(value) {
self.onmessageerror = value;
},
});
function defineHandlerAttribute(name: string, event: string) {
let handler: any = null;
const wrapper = (e: Event) => {
if ($isCallable(handler)) handler.$call(fake, e);
else if ($isCallable(handler?.handleEvent)) handler.handleEvent.$call(handler, e);
};
Object.defineProperty(fake, name, {
get() {
return handler;
},
set(value) {
const next = $isCallable(value) || (value !== null && typeof value === "object") ? value : null;
if (handler === null && next !== null) self.addEventListener(event, wrapper);
else if (handler !== null && next === null) self.removeEventListener(event, wrapper);
handler = next;
},
});
}
defineHandlerAttribute("onmessage", "message");
defineHandlerAttribute("onmessageerror", "messageerror");

const postMessage = $newCppFunction("ZigGlobalObject.cpp", "jsFunctionPostMessage", 1);
Object.defineProperty(fake, "postMessage", {
Expand Down Expand Up @@ -832,6 +835,10 @@ let parentPort: MessagePort | null = isMainThread ? null : fakeParentPort();
// Gate on _isNodeWorker so a raw `new globalThis.Worker` that transitively loads
// this module does NOT have process.abort/chdir/setuid replaced.
if (!isMainThread && _isNodeWorker) {
// Node has no global onmessage; parentPort shares this event target, so keeping it double-delivers.
try {
delete (globalThis as any).onmessage;
} catch {}
applyWorkerProcessOverrides();
}
function applyWorkerProcessOverrides() {
Expand Down
56 changes: 56 additions & 0 deletions test/js/node/worker_threads/worker_threads.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1756,3 +1756,59 @@ test("the SHARE_ENV founding thread's process.env stays live after the swap", as
expect(stdout.trim()).toBe("yes,unset");
expect(exitCode).toBe(0);
});

// https://github.com/oven-sh/bun/issues/7816
// Emscripten's pthread worker shim registers parentPort.on("message", ...) and
// then assigns self.onmessage. In Node the global onmessage is not an event
// handler accessor, so only the parentPort listener fires; Bun was delivering
// the message to both, so the pthread 'load' handler ran twice and aborted.
test("globalThis.onmessage is not an event handler accessor inside a node:worker_threads worker", async () => {
const worker = new Worker(
/* js */ `
const { parentPort } = require("worker_threads");
let count = 0;
const hadOnMessage = "onmessage" in globalThis;
Object.assign(global, { self: global });
parentPort.on("message", data => {
globalThis.onmessage({ data });
});
self.onmessage = e => {
count++;
if (e.data === "ping") {
setImmediate(() => parentPort.postMessage({ count, hadOnMessage }));
}
};
parentPort.postMessage("ready");
`,
{ eval: true },
);
try {
await once(worker, "message"); // "ready"
worker.postMessage("ping");
const [result] = await once(worker, "message");
expect(result).toEqual({ count: 1, hadOnMessage: false });
} finally {
await worker.terminate();
}
});
Comment thread
robobun marked this conversation as resolved.

test("parentPort.onmessage works inside a node:worker_threads worker", async () => {
const worker = new Worker(
/* js */ `
const { parentPort } = require("worker_threads");
parentPort.onmessage = e => {
parentPort.postMessage({ echoed: e.data, handlerIsSet: typeof parentPort.onmessage === "function" });
};
parentPort.postMessage("ready");
`,
{ eval: true },
);
try {
await once(worker, "message"); // "ready"
worker.postMessage("hello");
const [result] = await once(worker, "message");
expect(result).toEqual({ echoed: "hello", handlerIsSet: true });
} finally {
await worker.terminate();
}
});
4 changes: 2 additions & 2 deletions test/napi/node-napi-tests/test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,8 @@ if (typeof Bun === "object") {
ResolveMessage,
ErrorEvent,
Worker,
onmessage,
onerror,
globalThis.onmessage,
globalThis.onerror,
);
}

Expand Down
Loading