zig-js

Threads

On this page 3

zig-js supports two complementary thread models:

ModelWhat sharesWhat crossesPrimary docs
Agent / worker isolationNo JS heap state; each OS thread owns a Context, global object, jobs, allocator state, and exception state.Structured-clone bytes and retained SharedArrayBuffer storage.Agents, Workers
Shared-realm ThreadOne Context, global object, heap, shape tree, and object identity.Same-realm function arguments and return values.Thread API, Phase 6, GIL removal

The shared-realm model is now true-parallel by default:

const ctx = try js.Context.createWith(gpa, .{ .enable_threads = true });

That installs Thread, Lock, Condition, ThreadLocal, ConcurrentAccessError, property-mode Atomics.*, and proposal-aligned Atomics.Mutex / Atomics.Condition. Spawned Threads run JavaScript concurrently on real OS threads over the GC-managed, thread-safe heap.

The serialized fallback is still supported when deterministic GIL interleavings or legacy compatibility are useful:

const ctx = try js.Context.createWith(gpa, .{
    .enable_threads = true,
    .gil = true,
});

The C API exposes the same choice with ZJSGlobalContextCreateThreaded(gil). Non-threaded contexts remain single-threaded and keep the original affinity rules.

Shipping Surface

AreaStatusVerification
Refcounted SharedArrayBuffer storageImplemented in src/shared_buffer.zig and typed-array storage in src/value.zig.Unit tests, test262 SAB / Atomics shards, TSan gates.
WebAssembly shared memory and atomic executionComplete atomic opcode execution, SeqCst RMW/CAS/fence, wait32/wait64/notify, fixed historical Memory buffers, and targeted termination interruption. Terminal proposal-script/TSan/scaling evidence remains in issue #287.Pinned threads/atomic.wast 372/372, 1,069-test root, focused overlapping-access TSan witnesses.
$262.agent and typed-array Atomics.wait / notify / waitAsyncImplemented in src/agent.zig with hooks in the interpreter and VM.Unit tests and real test262 agent cases.
Structured clone and ArrayBuffer transfer/detachImplemented in src/structured_clone.zig.Unit tests, workers, and agents.
Embedder Worker APIImplemented in src/worker.zig with C-API hooks in src/c_api.zig.Worker unit tests, exact host-hook wake coverage, and C-API round trips.
Shared-realm Thread APIImplemented in src/jsthread.zig, src/gil.zig, and src/context.zig; parallel by default, GIL opt-out available.PR-249 coverage: 248 promoted of 259 executable files (243 default plus 5 parallel_js-only); the inventory checksums all 339 files and distinguishes 5 optimizing-tier blockers from 6 terminal premises. Disposition probes, no-GIL TSan, and fuzzers guard the surface.
Concurrent GC / root safetyGC-managed parallel contexts use thread-safe allocation, write barriers, per-structure locks, precise VM frame roots, and conservative native-stack rooting where applicable.Unit tests, parallel_gc soak, no-GIL corpus TSan, test262-parallel.

Core Rules

  • Context.createWith(.{ .enable_threads = false }) installs no Thread globals and keeps the original single-thread affinity rule.
  • Context.createWith(.{ .enable_threads = true }) runs shared-realm Threads in parallel by default and implies the GC-managed, thread-safe cell path.
  • Context.createWith(.{ .enable_threads = true, .gil = true }) keeps the same JavaScript API but serializes execution behind the context GIL.
  • Blocking APIs (join, Lock, Condition, typed-array Atomics wait, and property-mode Atomics wait) use their own synchronization paths in no-GIL mode and release the context GIL in serialized mode.
  • Object shapes, named properties, elements/collections, environments, promises, microtasks, inline caches, thread records, waiter queues, and shared-buffer storage each have explicit synchronization. New mutable shared state must follow that pattern.
  • JavaScript program races are distinct from engine-state races. See Memory Model for the public contract and the ThreadSanitizer suppression boundary.
  • Test-only knobs such as parallel_js and parallel_midscript_gc remain internal harness controls. They are not stable embedder APIs.
  • Process-global mutable state must be listed in bindings.md with a per-thread, locked, or refused ruling.

Reading Order