Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 6 additions & 5 deletions src/jsc/bindings/webcore/WorkerMessagingProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,11 +543,12 @@ void WorkerMessagingProxy::workerGlobalScopeDestroyedInternal(int32_t exitCode,
ASSERT(m_scriptExecutionContext && m_scriptExecutionContext->isContextThread());
Ref protectedThis { *this };

// node:worker_threads: a worker stopped by its parent once it was running reports 1 unless it
// called process.exit() itself (a process.exitCode it merely set is not used, as in Node). The
// Web Worker's 'close' event keeps 0 for that case (documented).
if (m_options.kind == WorkerOptions::Kind::Node && stoppedByParent)
exitCode = 1;
// A worker stopped by its parent that never called process.exit() did not choose an exit code
// (a process.exitCode it merely set is not used, as in Node). node:worker_threads reports 1 for
// that case like Node; the Web Worker's 'close' event reports 0 (documented), whether the
// terminate() landed while the entry module was still loading or once the loop was idle.
if (stoppedByParent)
exitCode = m_options.kind == WorkerOptions::Kind::Node ? 1 : 0;
Comment thread
robobun marked this conversation as resolved.
Resolved

// Closing while 'close' dispatches so handlers observe threadId == -1 / !isOnline() but a
// postMessage() from inside them is still accepted and dropped (browser/Node behaviour).
Expand Down
18 changes: 18 additions & 0 deletions test/js/web/workers/worker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,24 @@ describe("web worker", () => {
w.terminate();
});

// The 'close' code is what the worker passed to process.exit(), or 0. A worker stopped by its
// parent did not choose one, whether terminate() lands while the entry module is still
// evaluating or once its loop is idle.
test.each([
// The first message is posted from the entry's top-level code, which then never yields.
["busy evaluating its entry module", "postMessage('up'); for (;;) {}"],
// A reply to a parent message comes from the worker's event loop, after the entry has loaded.
["idle in its event loop", "self.onmessage = () => postMessage('up'); setInterval(() => {}, 1000)"],
])("close code is 0 after terminate() of a worker that is %s", async (_, body) => {
const w = new Worker("data:text/javascript," + encodeURIComponent(body));
w.postMessage("ping");
await once(w, "message");
const closed = once(w, "close");
w.terminate();
const [event] = await closed;
expect({ code: event.code, wasClean: event.wasClean }).toEqual({ code: 0, wasClean: true });
});

// As in browsers and Node: not an error, the message is dropped.
test("postMessage() to a terminated worker is a no-op", async () => {
const w = new Worker("data:text/javascript,postMessage('up')");
Expand Down
Loading