Skip to main content

Module worker_threads

Module worker_threads 

Source
Expand description

Node worker_threads: real OS-thread workers with fully isolated heaps.

§Model (matches Node: workers do NOT share the JS heap)

node-js’s entire runtime — the JsHost heap, the module cache, the event loop channel — lives in thread_local!s (host::HOST, module’s statics). So spawning a fresh OS thread automatically gives that thread its OWN isolated interpreter and heap. A Worker here is therefore a real std::thread that calls crate::eval_file/eval_str on the worker file, running it against that thread’s own clean thread_local host. Nothing on the JS heap is shared between the main thread and a worker, or between two workers — exactly Node’s isolation guarantee.

§Why messages cross as JSON strings, never Values

fusevm::Value is a per-thread heap handle (Value::Obj(u32) indexes the calling thread’s JsHost.heap); it is neither Send nor meaningful on another thread. So a message can NEVER be a Value. Every message crosses the thread boundary as a plain String of JSON:

  sender thread:   value ─JSON.stringify([value])→ String  (on sender's heap)
  channel:         String  (Send)
  receiver thread: String ─JSON.parse(s)[0]→ value        (on receiver's heap)

The value is wrapped in a one-element array before JSON.stringify so that top-level primitives AND undefined round-trip through a single always-valid JSON document (JSON.stringify(undefined) is itself undefined, not a string — the array wrapper avoids that). Deserialization unwraps [0] on the receiving thread’s own heap.

§Serialization is a JSON subset of structured clone (documented limitation)

Only JSON-serializable data transfers: objects, arrays, strings, numbers, booleans, null. undefined becomes null (JSON semantics), and functions, symbols, Map/Set, cycles, BigInt, and ArrayBuffer transfers are NOT supported (a BigInt makes JSON.stringify throw, surfaced as a thrown error from postMessage, matching Node’s DataCloneError in spirit). This is an honest subset, never a silent fake.

§Bidirectional message flow (both directions are real)

  • worker → main (parentPort.postMessage): the worker serializes on its own heap and posts an IoTask onto the MAIN loop’s io_sender (captured at construction). The task runs on the main thread, deserializes into a fresh Value on the main heap, and emits 'message' on the Worker object.
  • main → worker (worker.postMessage): the main thread serializes and sends the JSON string over an mpsc channel to the worker. A per-worker “bridge” thread (started when the worker adds a parentPort 'message' listener) forwards each string as an IoTask onto the WORKER loop’s io_sender; the task runs on the worker thread, deserializes on the worker heap, and emits 'message' on parentPort. The bridge is required because the worker’s event loop (host::run_event_loop) blocks only on its own I/O channel and this module cannot modify it — same pattern net uses for socket reads.

§Liveness

new Worker incr_handles the MAIN loop so the process stays alive while the worker runs; the worker’s 'exit' decr_handles it. On the worker side, registering a parentPort 'message' listener incr_handles the WORKER loop (keeping the worker alive to receive messages), and terminate releases it.

§terminate is cooperative (documented limitation)

Rust has no safe thread cancellation, so terminate signals the worker (via the bridge) to decr_handle and let its event loop unwind; a worker parked in its message loop exits promptly. A worker spinning in a tight synchronous JS loop is not force-killed — there is no safe preemption point. terminate returns undefined (awaiting it resolves to undefined).

Constants§

BROADCAST_CHANNEL_METHODS
Instance methods on a BroadcastChannel object.
METHODS
Module-level worker_threads functions. NOTE: these route only if the parent adds a "worker_threads" arm to stdlib::is_method and stdlib::call (the module previously had no callable methods) — see the report.
PORT_METHODS
Instance methods on a MessagePort (the worker-side parentPort), beyond the shared EventEmitter surface.
WORKER_METHODS
Instance methods on a Worker (main-side handle), beyond the shared EventEmitter surface (on/once/emit/…).

Functions§

call
Module-level dispatch. Routes only if the parent adds a "worker_threads" arm to stdlib::call (see the report).
constant
Non-function members of the worker_threads namespace: isMainThread/threadId/parentPort/workerData, and the Worker constructor (as a Builtin("Worker") so new Worker(...) reaches construct_worker).
construct_broadcast_channel
new BroadcastChannel(name) → an object that broadcasts postMessage data to every OTHER BroadcastChannel of the same name.
construct_message_channel
new MessageChannel() → { port1, port2 }, two MessagePort objects linked so that port1.postMessage(v) is delivered to port2 (and vice versa). Both ports live on the calling thread and share its heap; messages are cloned through the JSON subset (serialize/deserialize) so a posted object is a copy, not a shared reference — matching structured clone’s copy semantics. Requires the parent to wire MessageChannel construction (see the report).
construct_worker
Build a Worker and spawn its OS thread (runs on the MAIN thread). The worker thread runs filename (a file path, or the code itself when options.eval is truthy) against its own fresh thread_local host.
instance_call