Skip to content

node:worker_threads: process.exit(code) in a worker raises 'error'; a Date arrives as a string through postMessage #57

Closed

Description

@hassidan

Test I ran

node worker_threads.ts
slice run worker_threads.ts
worker_threads.ts
import { Worker } from "node:worker_threads";
const show = async (k: string, f: () => unknown) => { let v; try { v = await Promise.race([Promise.resolve().then(f), new Promise((r) => setTimeout(() => r("NEVER SETTLED (2000 ms)"), 2000))]); } catch (e: any) { v = "THROWS " + e.name + ": " + String(e.message).slice(0, 100); } console.log(k.padEnd(52) + JSON.stringify(v)); };
(async () => {
  await show("worker: process.exit(3) -> 'exit' code", () => new Promise((res) => { const w = new Worker(`process.exit(3)`, { eval: true }); w.on("exit", (c) => res("exit " + c)); w.on("error", (e: any) => res("error event: " + String(e.message).slice(0, 60))); }));
  await show("worker: normal completion -> 'exit' code", () => new Promise((res) => { const w = new Worker(`1+1`, { eval: true }); w.on("exit", (c) => res("exit " + c)); w.on("error", (e: any) => res("error event: " + e.message)); }));
  await show("worker: Date survives postMessage", () => new Promise((res, rej) => { const w = new Worker(`const { parentPort } = require("node:worker_threads"); parentPort.on("message", (m) => parentPort.postMessage([m.d instanceof Date, Object.prototype.toString.call(m.d), typeof m.d]));`, { eval: true }); w.on("message", (m) => { Promise.resolve(w.terminate()).then(() => res(m)); }); w.on("error", rej); w.postMessage({ d: new Date(0) }); }));
  await show("worker -> parent: Date survives", () => new Promise((res, rej) => { const w = new Worker(`require("node:worker_threads").parentPort.postMessage({ d: new Date(0), e: new Error("x"), r: /a/g })`, { eval: true }); w.on("message", (m) => { Promise.resolve(w.terminate()).then(() => res([m.d instanceof Date, m.e instanceof Error, m.r instanceof RegExp])); }); w.on("error", rej); }));
})();

Result

Node v24.22.2:

worker: process.exit(3) -> 'exit' code              "exit 3"
worker: normal completion -> 'exit' code            "exit 0"
worker: Date survives postMessage                   [true,"[object Date]","object"]
worker -> parent: Date survives                     [true,true,true]

Slice v0.10.7:

worker: process.exit(3) -> 'exit' code              "error event: Uncaught Error: process.exit(3)\n    at <main> (unknown at :1"
worker: normal completion -> 'exit' code            "exit 0"
worker: Date survives postMessage                   [false,"[object String]","string"]
worker -> parent: Date survives                     [false,true,true]

What is wrong

  1. process.exit(3) inside a worker makes the parent receive an 'error' event (Uncaught Error: process.exit(3)) instead of 'exit' with code 3. This is the same mechanism as the top-level process.exit behaviour reported separately, seen from the worker side.
  2. A Date posted in either direction is received as a string (typeof is "string"), so m.d.getTime() throws in the receiver. Map, Uint8Array, BigInt, Error and RegExp survive the same round trip, and structuredClone() in a single isolate preserves Date, so only the worker serialisation path is affected.

Worker start-up, workerData, message round trips, MessageChannel and receiveMessageOnPort matched Node in the same run.

Environment

  • Slice v0.10.7, commit ea7c70c, built with cargo +1.96.0 build --release --locked --bin slice
  • Reference: Node.js v24.22.2 (same script, same machine)
  • Ubuntu 26.04, Linux 7.0, x86_64
  • Item 1 is identical on the v0.10.5 tag.

Activity

  1. siktec-lab commented on Nov 11, 2026

    @siktec-lab
    Collaborator

    Fixed in PR #65 (v0.10.9):

    • Preserved Date instances (including Invalid Date via this[k] instanceof Date before Date.prototype.toJSON runs) as well as NaN and ±Infinity across workerData, postMessage, and MessageChannel.
    • Wired process.exit(code) inside Worker threads directly to the native __slice_process_exit(code) unwind so workers terminate immediately with the exact exit code even when process is referenced (Shard 18 loaded).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions