Canonical Tracker
This is the single canonical issue for JavaScript multithreading in zig-js. Older duplicate roadmap issues should point here instead of tracking similar work in parallel.
Authoritative design and status docs live in docs/threads:
docs/threads/index.md - start here and support matrix.
docs/threads/api.md - current shared-realm thread API.
docs/threads/memory-model.md - JS program races, engine-state races, and the TSan suppression boundary.
docs/threads/testing.md - exact Zig 0.17-dev verification commands.
docs/threads/production-readiness.md - no-GIL production-readiness status.
docs/threads/limits.md - unsupported surfaces and remaining roadmap.
docs/threads/bindings.md - mutable-state rulings.
docs/threads/P7-gc-design.md, docs/threads/P7-gil-removal.md, and docs/threads/P8-structs.md - GC, no-GIL, and TC39 structs planning.
Current State
- Layer A isolated agents / workers / shared memory are implemented: real OS-thread agents, retained
SharedArrayBuffer storage, typed-array Atomics.wait / notify / waitAsync, structured clone, ArrayBuffer transfer/detach, and Worker APIs are covered by unit and conformance tests.
- Shared-realm
Thread is implemented behind Context.createWith(.{ .enable_threads = true }): Thread, Lock, Condition, ThreadLocal, ConcurrentAccessError, property-mode Atomics.*, and proposal-aligned Atomics.Mutex / Atomics.Condition entry points.
enable_threads runs shared-realm Threads true-parallel by default over the GC-managed, thread-safe heap. The serialized fallback is explicit: Context.createWith(.{ .enable_threads = true, .gil = true }).
- The C API exposes the same choice through
ZJSGlobalContextCreateThreaded(gil). Non-threaded contexts remain single-threaded and keep the original affinity rules.
- Shape transitions, named-property metadata and slots, indexed storage, environments, promises, microtasks, inline caches, thread records, waiter queues, shared-buffer storage, host-side thread queues, release-function lock records, contended
Lock.hold native callback roots, and GC roots/barriers all have explicit synchronization and tracing paths.
Lock.asyncHold() ordering is covered, including the no-fn immediate-grant exclusion from synchronous Lock.hold() until the release function is delivered and called, and the deterministic barging witness where a sync hold legally overtakes a queued no-fn async ticket before await delivers its release function.
- Spawned threads settle their own typed-array
Atomics.waitAsync waiter list before publishing normal completion, so Thread.asyncJoin() can assimilate thread-returned waitAsync promises. Spawned-thread error/termination unwinds abandon child-owned typed-array waitAsync tickets before the child interpreter's stack-owned waiter-list token disappears.
- JS-level races are documented separately from engine races: JS program state can still race without synchronization, while engine-state races are bugs and remain TSan-gated. The TSan suppression boundary is documented in
docs/threads/memory-model.md and guarded by the suppression witness.
- The WebKit PR-249 thread allowlist is currently 224/224 green. Remaining reference-only files are tracked in
docs/threads/testing.md with concrete reasons, and zig build threads-reference-audit fails if any non-allowlisted executable file lacks a blocker classification; python3 tools/threads-reference-audit.py --probe-candidates lists the closest focused promotion probes before allowlist changes.
- CI gates every PR and main push with unit/corpus coverage plus no-GIL checks: TSan unit gates, a sharded no-GIL PR-249 corpus TSan sweep, suppression-narrowness witness, test262-parallel representative slice, and seeded concurrent-JS fuzzing (
threadfuzz, TSan fuzzer, amplified fuzzer, broad semantic fuzzer, mid-script-GC wait-pump/microtask/creator-buffer/sync-wait-cleanup/promise/teardown/Worker-SAB/Worker-exception/Worker-close/weak-collection fuzzer, lifecycle fuzzer, ReleaseSafe fuzzer, deterministic-result verifier).
- The lifecycle fuzzer covers parked/unjoined Thread teardown, Worker/thread overlap, module Worker/thread overlap, module Worker termination with shared-realm teardown/reaction/cleanup, Worker/thread/finalization scheduling on one retained SAB, Worker termination plus exact shared-realm finalization cleanup, Worker termination while top-level failure tears down parked shared-realm
Threads, pending asyncJoin rejection reactions, and already-ready cleanup jobs on the same retained SAB, exact script/module Worker close/terminate/postMessage drain/drop ordering, Worker handler-exception recovery, Worker handler-exception recovery composed with shared-realm Thread finalization cleanup on one retained SAB, module Worker handler-exception recovery composed with the same retained-SAB cleanup oracle, Thread.restrict lifecycle isolation, Thread exception identity through join() / asyncJoin() while waiters are parked, thread-returned typed-array waitAsync promise assimilation through join() / asyncJoin(), cross-thread FinalizationRegistry cleanup oracles, cleanup delivery interleaved with join() / asyncJoin() plus unregister-token suppression, cleanup delivery after parked property/condition waiters resume, typed-array waitAsync settlement interleaved with asyncJoin reactions and exact finalization cleanup, Condition.asyncWait reacquire delivery interleaved with join() / asyncJoin() reactions and exact finalization cleanup, teardown termination with pending asyncJoin rejection reactions and child-owned typed-array waitAsync ticket abandonment, teardown termination while property waitAsync timeout compaction, async condition reacquire, a pending asyncJoin rejection reaction, and already-ready FinalizationRegistry cleanup jobs share the same realm turn, Lock.asyncHold() barging, Promise reaction queue churn from with-fn Lock.asyncHold, no-fn release-function delivery, typed-array waitAsync, Thread.asyncJoin, and exact FinalizationRegistry cleanup, creator-owned SharedArrayBuffer and ArrayBuffer storage that survives the creating Thread's exit, sibling-thread reads, GC pressure, post-creator ArrayBuffer.transfer(), and isolated Worker structured-clone validation after the creator Thread exits, Thread.restrict-owned FinalizationRegistry cleanup after owner-thread exit, ThreadLocal isolation, and ThreadLocal finalization cleanup. Each seed runs 31 deterministic lifecycle subprograms.
- The mid-script GC fuzzer now runs 13 deterministic subprograms per seed. The wait-pump subprogram blocks peers in property
Atomics.wait, Condition.wait, and contended Lock acquisition while allocation pressure drives parallel_midscript_gc; it exercises queued async-hold grants including a root-bearing rejected grant, async condition reacquire, typed-array waitAsync waiter/reaction roots, pending Thread.asyncJoin fulfillment/rejection reaction roots, ThreadLocal hidden roots, completed-but-unjoined Thread result/exception roots, and exact FinalizationRegistry cleanup with unregister-token suppression. The sync-wait cleanup subprogram parks peers in property Atomics.wait, Condition.wait, and contended Lock.hold acquisition through a finishing sweep, verifies each resumed peer's stack root plus exact FinalizationRegistry cleanup count/sum delivery, lets expired property waitAsync tickets compact while those sync peers are parked, keeps one live property waitAsync ticket rooted through the sweep, then notifies it and verifies exact captured-root scoring. The promise-publication subprogram leaves a child-returned typed-array waitAsync promise, a child-returned rejected promise, a child-returned user thenable, and a child-thrown object rooted through thread completion/native waiter state across a finishing sweep, then verifies post-sweep Thread.asyncJoin() fulfillment/rejection/thenable assimilation, Thread.join() returning the child promise/thenable for post-completion observers, thrown-object publication, and nested thrown/rejected promises. The pending-microtask subprogram queues Promise, typed-array waitAsync, Thread.asyncJoin, with-fn Lock.asyncHold, no-fn release-function, and FinalizationRegistry cleanup roots through a finishing mid-script sweep, then drains the realm run loop and verifies exact reaction and cleanup oracles. The creator-owned buffer subprogram leaves child-created SharedArrayBuffer and ArrayBuffer storage rooted through unjoined Thread completion records and delayed asyncJoin observers across a finishing sweep, then verifies blocking join(), post-sweep asyncJoin(), and ArrayBuffer.transfer() observers see exact contents after the creating thread has exited. The script Worker/SAB and module Worker/SAB cleanup subprograms run isolated Workers on the same retained SharedArrayBuffer while shared-realm Threads register cleanup targets and park stack roots through a finishing sweep, then verify exact Worker progress, joined thread roots, asyncJoin reactions, and cleanup count/sum; sibling script/module Worker handler-exception cleanup subprograms first recover from an expected thrown onmessage delivery, then prove the same Worker progress and cleanup oracle through the finishing sweep. Script/module Worker close/terminate subprograms preserve exact FIFO drain/drop, post-close drop, post-terminate receive silence, joined roots, asyncJoin reactions, and cleanup count/sum through the same finishing sweep. The weak-collection subprogram parks property Atomics.wait, Condition.wait, and contended Lock.hold peers while live WeakMap values are reachable only through live weak keys, dead WeakMap/WeakSet targets are reachable only through weak structures and WeakRefs, and FinalizationRegistry unregister-token records compact through a finishing sweep, then verifies live ephemeron values, cleared dead refs, exact cleanup count/sum, and exact unregister suppression. The teardown subprogram parks children after installing child-owned typed-array waitAsync tickets, drives a finishing mid-script parallel sweep, then parent failure triggers teardown; it verifies pending asyncJoin rejection reactions with captured roots and proves post-termination notify wakes zero leaked child waitAsync tickets.
Work Still Tracked Here
- Keep
docs/threads and this issue aligned whenever thread behavior, counts, blockers, or public API wording changes.
- Promote PR-249 files only when the engine implements the behavior and the file passes reliably under Zig
0.17-dev; keep tools/threads-reference-audit.py accurate whenever the reference-only tail changes.
- Improve GC performance: the first GC cell-slab allocation fast path is landed, fresh slab chunks lazily bump cells with a per-bucket bump hint instead of pre-linking every unused slot, slab ownership lookup is bucket-local with address-span rejects and per-bucket recent-chunk hints before chunk-list scans, context destroy skips rebuilding slab freelists and per-cell bucket ownership reclassification during bulk teardown, object finalization skips the object-backing walker when a reclaimed object has no side stores, live
SharedArrayBuffer retain teardown is regression-covered across arena/no-GIL threaded/.gil = true contexts, and single-mutator GC object side stores now bypass the cell-slab classifier while true-parallel JS contexts keep the synchronized backing wrapper. zig build gc-profile now splits context lifecycle attribution into create and destroy columns, includes an embedder task lifecycle table, and prints GC cell-backing attribution for object-heavy allocation runs. Continue nursery/generational work and reduce remaining create/destroy overhead.
- Profile and optimize parallel scaling bottlenecks with
zig build threads-profile: it compares no-GIL and .gil = true timings across independent compute, shared object properties, array append, typed-array Atomics, property Atomics.wait / notify, property Atomics.waitAsync timeout settlement, Condition.wait / notifyAll, Condition.asyncWait, contended Lock.hold, Lock.asyncHold delivery, observed callback settlement, no-fn release-function delivery, lifecycle churn, and an isolated Worker section. Empty pumps skip the task lock, async-hold delivery uses FIFO head cursors and bounded FIFO bursts, Condition queues use a FIFO head cursor and canceled sync tombstones, sync notifyAll handoff waits on the waiter's condition ack signal instead of sleeping in fixed 1ms polling chunks, and async-only condition notifications prepare no-fn async regrants after releasing the condition queue mutex, Promise microtask drains use a FIFO head cursor instead of front-shifting reaction queues, async-hold task pumps snapshot the microtask enqueue generation around each delivered grant so unobserved grants skip empty no-GIL microtask drains, no-fn async-hold grants embed their once-only release state in the already arena-lived hold job to avoid an extra small allocation per delivered release function, property-mode Atomics.notify / sync wait timeout/termination cleanup / waitAsync timeout and teardown paths compact/scan in one pass, Worker channels use FIFO head cursors, empty Worker.receive(..., 0) polls skip drained-queue compaction, unordered root lists use swap removal for active interpreters, protected C-API handles, and GIL park records, WeakMap/WeakSet delete and GC dead-key pruning use unordered tail removal, and FinalizationRegistry unregister uses one stable compaction pass, typed-array Atomics.notify unlinks sync stack tickets before signal, and typed-array waitAsync harvest/abandon paths stable-compact matching tickets in one pass while preserving FIFO order for remaining waiters. threads-profile now also prints async/done columns for Condition.asyncWait and property waitAsync, with direct rows for property finite-timeout settlement and async condition reacquire delivery; property timeout rows should keep registration and settlement counts equal, and condition async rows now count completed reacquires so final-batch regrant pressure is visible in the same table. Keep using the attribution columns and Worker table to drive reductions in global/environment bindings, object property/element locks, sync waiters, property waitAsync timeout settlement, async condition regrant delivery and its remaining run-loop job cost, user-level locks, lifecycle join waiting, unobserved async-hold grant delivery, promise-observed callback settlement, no-fn release-function delivery, collection helpers, and GC allocation under high thread counts.
- Maintain the memory-model documentation as new synchronization primitives, TSan suppressions, and corpus coverage land.
- Mature mid-script parallel GC: sync-wait pump points publish roots for property
Atomics.wait, Condition.wait, and contended Lock acquisition; host-side thread queues (Gil.tasks, per-lock pending async grants, async condition waiters, typed-array waitAsync waiter/reaction roots, pending Thread.asyncJoin promise/reaction roots, ThreadLocal values, thread completion results, and release-function lock records) trace or barrier hidden JS values; contended Lock.hold receiver/callback pairs are temp-rooted while native acquisition parks and pumps. Parked property/condition/contended-lock peers plus finalization cleanup, expired property waitAsync timeout compaction, and one live property waitAsync ticket rooted through a finishing sweep are now covered by the focused mid-GC sync-wait cleanup subprogram, termination with pending asyncJoin/waitAsync roots is covered by the mid-GC teardown subprogram, isolated script Worker/SAB and module Worker/SAB progress plus script/module Worker handler-exception recovery and script/module Worker close/terminate drain/drop while shared-realm cleanup roots are swept are covered by the mid-GC Worker cleanup subprograms, returned fulfilled/rejected promises, user thenables, and thrown-object publication are covered by the mid-GC promise-publication subprogram, pending Promise/microtask roots across asyncHold callback/release delivery, typed-array waitAsync, Thread.asyncJoin, and cleanup reactions are covered by the mid-GC microtask-churn subprogram, creator-owned SharedArrayBuffer / ArrayBuffer backing storage rooted through unjoined Thread completion and delayed asyncJoin observers is covered by the mid-GC creator-buffer subprogram, and live/dead WeakMap/WeakSet and FinalizationRegistry unregister-token compaction is covered by the mid-GC weak-collection subprogram. Joiners now publish gc_parked only for the actual native condition wait, and requested shell/host GC leaves elected mid-script parallel collectors untouched while threads are live; keep adding more waiter cleanup, deeper cross-thread lifecycle teardown variants, and additional worker/cleanup combinations.
- Keep expanding
threadfuzz and hand-written stress toward more cross-realm scheduling, teardown ordering variants, richer cleanup/finalization interleavings, worker/thread lifecycle, backing-store lifetime hazards, and cross-thread teardown.
- Keep C-API context affinity rules explicit; do not expose test-only host knobs (
parallel_js, parallel_midscript_gc, shell $vm hooks) as stable embedder APIs until they have a real public contract.
- Track TC39
proposal-structs only through this issue and docs/threads/P8-structs.md; do not open another parallel structs/threading tracker.
Verification Gates
Use Zig 0.17-dev:
zig build test
zig build threads-test
zig build threads-reference-audit
python3 tools/threads-reference-audit.py --probe-candidates
zig build threads-test -Dthreads-case=api/blocking-gate.js
zig build threads-test -Dthreads-case=api/condition-async-wait.js
zig build threads-test -Dthreads-case=api/lock-hold-termination.js
zig build threads-test -Dthreads-case=atomics/ta-wait-thread-gate.js
zig build threads-test -Dthreads-case=atomics/property-waitasync-timeout.js
zig build threads-test -Dthreads-parallel-js=true -Dthreads-case=sync/condition-wait-notify.js
zig build threads-test -Dthreads-parallel-js=true -Dthreads-case=api/lock-async-hold.js
zig build test -Dtsan=true
zig build test -Dtsan=true -Dtest-filter=parallel_js
zig build threadfuzz -Dfuzz-iters=20
zig build threadfuzz -Dfuzz-midgc=true -Dfuzz-iters=20
zig build threadfuzz -Dfuzz-lifecycle=true -Dfuzz-iters=20
zig build threadfuzz -Dfuzz-verify=true -Dfuzz-iters=300
zig build threads-profile
zig build gc-profile
bun run docs:build
Thread work should also run focused PR-249 cases for the touched behavior, update allowlist/count docs when promotion is real, keep docs/threads/bindings.md current for every new mutable global or threadlocal state, use python3 tools/threads-reference-audit.py --probe-candidates before promotion attempts, and rely on CI for the sharded no-GIL corpus TSan sweep / suppression witness / test262-parallel legs when they are too expensive locally.
Canonical Tracker
This is the single canonical issue for JavaScript multithreading in zig-js. Older duplicate roadmap issues should point here instead of tracking similar work in parallel.
Authoritative design and status docs live in
docs/threads:docs/threads/index.md- start here and support matrix.docs/threads/api.md- current shared-realm thread API.docs/threads/memory-model.md- JS program races, engine-state races, and the TSan suppression boundary.docs/threads/testing.md- exact Zig0.17-devverification commands.docs/threads/production-readiness.md- no-GIL production-readiness status.docs/threads/limits.md- unsupported surfaces and remaining roadmap.docs/threads/bindings.md- mutable-state rulings.docs/threads/P7-gc-design.md,docs/threads/P7-gil-removal.md, anddocs/threads/P8-structs.md- GC, no-GIL, and TC39 structs planning.Current State
SharedArrayBufferstorage, typed-arrayAtomics.wait/notify/waitAsync, structured clone, ArrayBuffer transfer/detach, and Worker APIs are covered by unit and conformance tests.Threadis implemented behindContext.createWith(.{ .enable_threads = true }):Thread,Lock,Condition,ThreadLocal,ConcurrentAccessError, property-modeAtomics.*, and proposal-alignedAtomics.Mutex/Atomics.Conditionentry points.enable_threadsruns shared-realmThreads true-parallel by default over the GC-managed, thread-safe heap. The serialized fallback is explicit:Context.createWith(.{ .enable_threads = true, .gil = true }).ZJSGlobalContextCreateThreaded(gil). Non-threaded contexts remain single-threaded and keep the original affinity rules.Lock.holdnative callback roots, and GC roots/barriers all have explicit synchronization and tracing paths.Lock.asyncHold()ordering is covered, including the no-fn immediate-grant exclusion from synchronousLock.hold()until the release function is delivered and called, and the deterministic barging witness where a sync hold legally overtakes a queued no-fn async ticket beforeawaitdelivers its release function.Atomics.waitAsyncwaiter list before publishing normal completion, soThread.asyncJoin()can assimilate thread-returned waitAsync promises. Spawned-thread error/termination unwinds abandon child-owned typed-arraywaitAsynctickets before the child interpreter's stack-owned waiter-list token disappears.docs/threads/memory-model.mdand guarded by the suppression witness.docs/threads/testing.mdwith concrete reasons, andzig build threads-reference-auditfails if any non-allowlisted executable file lacks a blocker classification;python3 tools/threads-reference-audit.py --probe-candidateslists the closest focused promotion probes before allowlist changes.threadfuzz, TSan fuzzer, amplified fuzzer, broad semantic fuzzer, mid-script-GC wait-pump/microtask/creator-buffer/sync-wait-cleanup/promise/teardown/Worker-SAB/Worker-exception/Worker-close/weak-collection fuzzer, lifecycle fuzzer, ReleaseSafe fuzzer, deterministic-result verifier).Threads, pendingasyncJoinrejection reactions, and already-ready cleanup jobs on the same retained SAB, exact script/module Worker close/terminate/postMessage drain/drop ordering, Worker handler-exception recovery, Worker handler-exception recovery composed with shared-realm Thread finalization cleanup on one retained SAB, module Worker handler-exception recovery composed with the same retained-SAB cleanup oracle,Thread.restrictlifecycle isolation, Thread exception identity throughjoin()/asyncJoin()while waiters are parked, thread-returned typed-arraywaitAsyncpromise assimilation throughjoin()/asyncJoin(), cross-threadFinalizationRegistrycleanup oracles, cleanup delivery interleaved withjoin()/asyncJoin()plus unregister-token suppression, cleanup delivery after parked property/condition waiters resume, typed-arraywaitAsyncsettlement interleaved withasyncJoinreactions and exact finalization cleanup,Condition.asyncWaitreacquire delivery interleaved withjoin()/asyncJoin()reactions and exact finalization cleanup, teardown termination with pendingasyncJoinrejection reactions and child-owned typed-arraywaitAsyncticket abandonment, teardown termination while propertywaitAsynctimeout compaction, async condition reacquire, a pendingasyncJoinrejection reaction, and already-readyFinalizationRegistrycleanup jobs share the same realm turn,Lock.asyncHold()barging, Promise reaction queue churn from with-fnLock.asyncHold, no-fn release-function delivery, typed-arraywaitAsync,Thread.asyncJoin, and exactFinalizationRegistrycleanup, creator-ownedSharedArrayBufferandArrayBufferstorage that survives the creating Thread's exit, sibling-thread reads, GC pressure, post-creatorArrayBuffer.transfer(), and isolated Worker structured-clone validation after the creator Thread exits,Thread.restrict-ownedFinalizationRegistrycleanup after owner-thread exit,ThreadLocalisolation, andThreadLocalfinalization cleanup. Each seed runs 31 deterministic lifecycle subprograms.Atomics.wait,Condition.wait, and contendedLockacquisition while allocation pressure drivesparallel_midscript_gc; it exercises queued async-hold grants including a root-bearing rejected grant, async condition reacquire, typed-arraywaitAsyncwaiter/reaction roots, pendingThread.asyncJoinfulfillment/rejection reaction roots,ThreadLocalhidden roots, completed-but-unjoined Thread result/exception roots, and exactFinalizationRegistrycleanup with unregister-token suppression. The sync-wait cleanup subprogram parks peers in propertyAtomics.wait,Condition.wait, and contendedLock.holdacquisition through a finishing sweep, verifies each resumed peer's stack root plus exactFinalizationRegistrycleanup count/sum delivery, lets expired propertywaitAsynctickets compact while those sync peers are parked, keeps one live propertywaitAsyncticket rooted through the sweep, then notifies it and verifies exact captured-root scoring. The promise-publication subprogram leaves a child-returned typed-arraywaitAsyncpromise, a child-returned rejected promise, a child-returned user thenable, and a child-thrown object rooted through thread completion/native waiter state across a finishing sweep, then verifies post-sweepThread.asyncJoin()fulfillment/rejection/thenable assimilation,Thread.join()returning the child promise/thenable for post-completion observers, thrown-object publication, and nested thrown/rejected promises. The pending-microtask subprogram queues Promise, typed-arraywaitAsync,Thread.asyncJoin, with-fnLock.asyncHold, no-fn release-function, andFinalizationRegistrycleanup roots through a finishing mid-script sweep, then drains the realm run loop and verifies exact reaction and cleanup oracles. The creator-owned buffer subprogram leaves child-createdSharedArrayBufferandArrayBufferstorage rooted through unjoinedThreadcompletion records and delayedasyncJoinobservers across a finishing sweep, then verifies blockingjoin(), post-sweepasyncJoin(), andArrayBuffer.transfer()observers see exact contents after the creating thread has exited. The script Worker/SAB and module Worker/SAB cleanup subprograms run isolated Workers on the same retainedSharedArrayBufferwhile shared-realmThreads register cleanup targets and park stack roots through a finishing sweep, then verify exact Worker progress, joined thread roots, asyncJoin reactions, and cleanup count/sum; sibling script/module Worker handler-exception cleanup subprograms first recover from an expected thrownonmessagedelivery, then prove the same Worker progress and cleanup oracle through the finishing sweep. Script/module Worker close/terminate subprograms preserve exact FIFO drain/drop, post-close drop, post-terminate receive silence, joined roots, asyncJoin reactions, and cleanup count/sum through the same finishing sweep. The weak-collection subprogram parks propertyAtomics.wait,Condition.wait, and contendedLock.holdpeers while live WeakMap values are reachable only through live weak keys, dead WeakMap/WeakSet targets are reachable only through weak structures and WeakRefs, and FinalizationRegistry unregister-token records compact through a finishing sweep, then verifies live ephemeron values, cleared dead refs, exact cleanup count/sum, and exact unregister suppression. The teardown subprogram parks children after installing child-owned typed-arraywaitAsynctickets, drives a finishing mid-script parallel sweep, then parent failure triggers teardown; it verifies pendingasyncJoinrejection reactions with captured roots and proves post-termination notify wakes zero leaked child waitAsync tickets.Work Still Tracked Here
docs/threadsand this issue aligned whenever thread behavior, counts, blockers, or public API wording changes.0.17-dev; keeptools/threads-reference-audit.pyaccurate whenever the reference-only tail changes.SharedArrayBufferretain teardown is regression-covered across arena/no-GIL threaded/.gil = truecontexts, and single-mutator GC object side stores now bypass the cell-slab classifier while true-parallel JS contexts keep the synchronized backing wrapper.zig build gc-profilenow splits context lifecycle attribution into create and destroy columns, includes an embedder task lifecycle table, and prints GC cell-backing attribution for object-heavy allocation runs. Continue nursery/generational work and reduce remaining create/destroy overhead.zig build threads-profile: it compares no-GIL and.gil = truetimings across independent compute, shared object properties, array append, typed-array Atomics, propertyAtomics.wait/notify, propertyAtomics.waitAsynctimeout settlement,Condition.wait/notifyAll,Condition.asyncWait, contendedLock.hold,Lock.asyncHolddelivery, observed callback settlement, no-fn release-function delivery, lifecycle churn, and an isolated Worker section. Empty pumps skip the task lock, async-hold delivery uses FIFO head cursors and bounded FIFO bursts, Condition queues use a FIFO head cursor and canceled sync tombstones, syncnotifyAllhandoff waits on the waiter's condition ack signal instead of sleeping in fixed 1ms polling chunks, and async-only condition notifications prepare no-fn async regrants after releasing the condition queue mutex, Promise microtask drains use a FIFO head cursor instead of front-shifting reaction queues, async-hold task pumps snapshot the microtask enqueue generation around each delivered grant so unobserved grants skip empty no-GIL microtask drains, no-fn async-hold grants embed their once-only release state in the already arena-lived hold job to avoid an extra small allocation per delivered release function, property-modeAtomics.notify/ sync wait timeout/termination cleanup /waitAsynctimeout and teardown paths compact/scan in one pass, Worker channels use FIFO head cursors, emptyWorker.receive(..., 0)polls skip drained-queue compaction, unordered root lists use swap removal for active interpreters, protected C-API handles, and GIL park records, WeakMap/WeakSet delete and GC dead-key pruning use unordered tail removal, and FinalizationRegistryunregisteruses one stable compaction pass, typed-arrayAtomics.notifyunlinks sync stack tickets before signal, and typed-arraywaitAsyncharvest/abandon paths stable-compact matching tickets in one pass while preserving FIFO order for remaining waiters.threads-profilenow also printsasync/donecolumns forCondition.asyncWaitand propertywaitAsync, with direct rows for property finite-timeout settlement and async condition reacquire delivery; property timeout rows should keep registration and settlement counts equal, and condition async rows now count completed reacquires so final-batch regrant pressure is visible in the same table. Keep using the attribution columns and Worker table to drive reductions in global/environment bindings, object property/element locks, sync waiters, propertywaitAsynctimeout settlement, async condition regrant delivery and its remaining run-loop job cost, user-level locks, lifecycle join waiting, unobserved async-hold grant delivery, promise-observed callback settlement, no-fn release-function delivery, collection helpers, and GC allocation under high thread counts.Atomics.wait,Condition.wait, and contendedLockacquisition; host-side thread queues (Gil.tasks, per-lock pending async grants, async condition waiters, typed-arraywaitAsyncwaiter/reaction roots, pendingThread.asyncJoinpromise/reaction roots, ThreadLocal values, thread completion results, and release-function lock records) trace or barrier hidden JS values; contendedLock.holdreceiver/callback pairs are temp-rooted while native acquisition parks and pumps. Parked property/condition/contended-lock peers plus finalization cleanup, expired propertywaitAsynctimeout compaction, and one live propertywaitAsyncticket rooted through a finishing sweep are now covered by the focused mid-GC sync-wait cleanup subprogram, termination with pending asyncJoin/waitAsync roots is covered by the mid-GC teardown subprogram, isolated script Worker/SAB and module Worker/SAB progress plus script/module Worker handler-exception recovery and script/module Worker close/terminate drain/drop while shared-realm cleanup roots are swept are covered by the mid-GC Worker cleanup subprograms, returned fulfilled/rejected promises, user thenables, and thrown-object publication are covered by the mid-GC promise-publication subprogram, pending Promise/microtask roots across asyncHold callback/release delivery, typed-arraywaitAsync,Thread.asyncJoin, and cleanup reactions are covered by the mid-GC microtask-churn subprogram, creator-ownedSharedArrayBuffer/ArrayBufferbacking storage rooted through unjoined Thread completion and delayedasyncJoinobservers is covered by the mid-GC creator-buffer subprogram, and live/dead WeakMap/WeakSet and FinalizationRegistry unregister-token compaction is covered by the mid-GC weak-collection subprogram. Joiners now publishgc_parkedonly for the actual native condition wait, and requested shell/host GC leaves elected mid-script parallel collectors untouched while threads are live; keep adding more waiter cleanup, deeper cross-thread lifecycle teardown variants, and additional worker/cleanup combinations.threadfuzzand hand-written stress toward more cross-realm scheduling, teardown ordering variants, richer cleanup/finalization interleavings, worker/thread lifecycle, backing-store lifetime hazards, and cross-thread teardown.parallel_js,parallel_midscript_gc, shell$vmhooks) as stable embedder APIs until they have a real public contract.proposal-structsonly through this issue anddocs/threads/P8-structs.md; do not open another parallel structs/threading tracker.Verification Gates
Use Zig
0.17-dev:Thread work should also run focused PR-249 cases for the touched behavior, update allowlist/count docs when promotion is real, keep
docs/threads/bindings.mdcurrent for every new mutable global or threadlocal state, usepython3 tools/threads-reference-audit.py --probe-candidatesbefore promotion attempts, and rely on CI for the sharded no-GIL corpus TSan sweep / suppression witness / test262-parallel legs when they are too expensive locally.