Open-Source Wikis

/

Node.js

/

Systems

/

Child processes and worker threads

nodejs/node

Child processes and worker threads

Purpose

Run additional Node (or external) processes (child_process) and additional Node threads inside the same process (worker_threads). Both produce isolated execution environments with structured channels back to the parent.

Directory layout

lib/
  child_process.js
  worker_threads.js
  cluster.js                     // multi-process cluster on top of child_process
  internal/child_process.js
  internal/worker.js             // Worker class JS
  internal/worker/               // Worker support modules
    js_transferable.js           // structured-clone primitives bridge
    io.js                        // MessagePort + parentPort
    messaging.js                 // postMessage / structured-clone integration
src/
  process_wrap.{cc,h}            // ProcessWrap : HandleWrap (uv_process_t)
  spawn_sync.{cc,h}              // spawnSync implementation
  node_worker.{cc,h}             // Worker C++ class (per-thread Environment)
  node_messaging.{cc,h}          // MessagePort + structured clone

Key abstractions

Type / file Role
ChildProcess (lib/internal/child_process.js) EventEmitter wrapping a ProcessWrap and the parent ends of stdio handles.
ProcessWrap (src/process_wrap.cc) HandleWrap over uv_process_t. Spawns and reaps the child.
SyncProcessRunner (src/spawn_sync.cc) Drives synchronous spawnSync using nested loops/timeouts.
Worker (lib/internal/worker.js) JS class for worker_threads.Worker.
Worker (src/node_worker.cc) C++ class that owns a child Environment + thread.
MessagePort (src/node_messaging.cc) Structured-clone-capable port; backs both Worker IPC and the WHATWG MessagePort.
MessageEvent / MessageChannel (src/node_messaging.cc) WHATWG-spec primitives bound to JS.

How child_process.spawn works

sequenceDiagram
    participant JS as child_process.spawn
    participant CP as ChildProcess
    participant PW as ProcessWrap
    participant uv as libuv uv_spawn
    participant K as kernel

    JS->>CP: spawn(file, args, opts)
    CP->>PW: spawn(uv_process_options_t)
    PW->>uv: uv_spawn (fork/exec on POSIX, CreateProcessW on Win)
    uv->>K: process created
    K-->>uv: pid
    uv-->>PW: ok
    PW-->>CP: ProcessWrap.pid
    CP-->>JS: ChildProcess instance
    K->>uv: SIGCHLD / process exit
    uv-->>PW: ExitCallback
    PW-->>CP: emit 'exit' / 'close'

stdio is implemented by handing libuv preconfigured handles (uv_pipe_t, uv_tty_t, file descriptors, IPC channels). The IPC channel uses a Pipe set up with IPC mode so that handle-passing (subprocess.send(msg, handle)) is supported.

child_process.fork is spawn with process.execPath and an inherited IPC channel; it loads the module path passed in.

How a Worker thread is created

graph TD
    JS[worker_threads.Worker] --> Wrap[node::worker::Worker C++]
    Wrap --> Iso[New v8::Isolate]
    Iso --> Env[New node::Environment]
    Env --> Realm[New principal Realm]
    Env --> Thread[std::thread runs uv loop]
    Realm --> Bootstrap[lib/internal/main/worker_thread.js]
    Bootstrap --> User[user worker entry]
    Wrap --> ParentPort[MessagePort pair]
  • Each worker has its own v8::Isolate, node::Environment, libuv loop, and OS thread. They share the V8 platform and the parent process address space (so SharedArrayBuffers work cross-thread).
  • The parent and child are linked by a MessagePort pair — worker.parentPort on the child, worker itself behaves as a port on the parent.
  • Worker.terminate() triggers a Wave: stop the loop, dispose the Environment, dispose the Isolate, join the thread.

Structured clone and transfer

MessagePort.postMessage(value, transferList) runs node::messaging::SerializeMessage (src/node_messaging.cc) which uses V8's ValueSerializer plus Node's transferable types:

  • MessagePort (closes the local port, reopens on the peer).
  • ArrayBuffer (transfer ownership).
  • SharedArrayBuffer, WebAssembly.Module (clone reference).
  • Blob (Node's Blob is fully transferable).
  • crypto.KeyObject (via JSTransferable plumbing in lib/internal/worker/js_transferable.js).
  • User-defined JSTransferable subclasses (used by FileHandle, URLPattern, etc.).

Cluster

lib/cluster.js is a thin wrapper using child_process.fork + IPC. The child receives sockets via handle-passing on the IPC channel (subprocess.send(msg, handle)). It is mostly a legacy module — modern deployments prefer worker threads or external orchestrators.

Permission model

  • child_process domain (src/permission/child_process_permission.cc) blocks all child-process spawning under --permission unless explicitly granted.
  • worker domain (src/permission/worker_permission.cc) similarly gates new Worker(...).

Integration points

  • Stdio: child stdio handles become net.Socket/Pipe instances on the parent's side, so they can be read with the same Streams API.
  • Inspector: workers run their own inspector agent; --inspect-brk-node debug option propagates.
  • Diagnostics channel: workers and child processes publish lifecycle events (child_process.spawn, child_process.exit, node:worker_threads:create).
  • Snapshots: workers boot via lib/internal/main/worker_thread.js, sharing the same embedded snapshot mechanism as the main thread.

Entry points for modification

  • Spawn semantics? lib/internal/child_process.js (JS) and src/process_wrap.cc (C++).
  • Sync exec? src/spawn_sync.cc.
  • Worker bootstrap? lib/internal/main/worker_thread.js and src/node_worker.cc.
  • MessagePort transfer? src/node_messaging.cc and lib/internal/worker/js_transferable.js.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Child processes and worker threads – Node.js wiki | Factory