ReviewOS

zig-utils/zig-js

Threads: optimize Thread, Worker, channel, and join lifecycle

#482
Open chris opened this 9 days ago ·
9 days ago

Parent: #459 Depends on: #461, #476, #478, #479

Goal

Reduce Thread, Worker, channel, shared-buffer, task, waiter, and join lifecycle overhead while preserving the distinct shared-realm and isolated-agent semantics.

Scope

  • Attribute OS-thread creation, runtime/Interpreter setup, root publication, task dispatch, joins, asyncJoin, message serialization/copy/transfer, queue growth/drain, shared-buffer refcounts, wait/notify parking, worker module startup, close/terminate, and creator-owned cleanup.
  • Add bounded reusable worker/thread runtime infrastructure only where public lifecycle identity, thread-local state, inspector targets, GC roots, termination, and teardown remain exact.
  • Optimize SPSC/MPSC channel operations, queue cursors/capacity, structured clone buffers, shared backing lifetime, and park/wake paths with explicit ownership and memory ordering.
  • Separate user-level lock contention from engine lifecycle cost and retain .gil = true as a semantic mode, not a performance workaround.

No-workaround rules

  • No hidden pre-running JavaScript Thread, skipped OS/runtime lifecycle inside a timer, message drop/coalescing, unbounded queue/buffer pool, handle-affinity weakening, or hard terminate that bypasses cleanup.

Acceptance

  • #460/#461 cover script/module Workers, shared Threads, messages, empty polls, transfer/SAB, wait/notify, sync/async joins, close, terminate, and repeated lifecycle.
  • Retained changes reduce attributed lifecycle/queue/wait cost with bounded RSS and exact operation counts.
  • Message caps, failure atomicity, ownership, inspector transport, finalizers, module graphs, GC roots, and creator-thread callbacks remain exact.
  • Worker/Thread corpus, lifecycle fuzz, no-GIL, TSan, OOM/fault, and leak gates pass.
  • Shared comparison warmup/timed boundaries remain documented and symmetric.

Foundation

#1/#40/#49 and the remaining profile targets in docs/threads/production-readiness.md.

9 days ago

8b1ae613 fixes a process-wide parallel-sync activation race found by sustained lifecycle seed 35.

Object.lockElements() previously reread the enable-only global gate independently in unlockElements(). During concurrent Context bootstrap, a call could bypass while the gate was false, then release after another host thread enabled it, aborting on trace-depth underflow and attempting an unmatched mutex unlock. Acquisition now returns a token consumed by every release site across the interpreter, VM, GC, structured clone, C API, and object helpers.

Verification: focused transition witness passes in Debug and TSan; five sustained seed-35 lifecycle runs cover 280 programs with zero failures; the full suite passes 1,478/1,478 across 10 shards with zero failures, skips, or leaks. Test262 flips: 0.

9 days ago

Additional sanitizer evidence for 8b1ae613: the historical mid-GC failing seed now passes both profiles. Seed 1 completes 45/45 programs in 34.95 s without TSan and 45/45 in 236.55 s under TSan, with zero functional failures and no sanitizer report.

9 days ago

Landed f5363e46 — fix(gc): trace suspended activations during recovery (#476).

Thread-lifecycle impact: evaluateWithThis() now roots a successful script result for the entire microtask/Thread keepalive interval. Previously, after the top-level VM returned a Promise, the last child exit could make a pending collection runnable while that Promise existed only in a native local; the wrapper was swept before evaluate() returned. A dedicated regression now forces that collection boundary and checks the Promise, settlement value, release lifecycle, and completed collection.

Exact-parent focused repro changed from 0/1 passing on clean origin/main to 20/20 passing; the 1,120-program lifecycle profile and targeted TSan coverage are green. This is a completed #482 correctness slice, not closure of the broader lifecycle/performance issue.

9 days ago

Cross-issue landing blocker found while validating the unlanded #531 Shape/Object candidate.

Exact clean-main control at 8034cdfa:

  • zig build threadfuzz -Dfuzz-iters=1 -Dfuzz-seed=10 -Dfuzz-midgc=true: 45/45 programs, 0 failures;
  • zig-out/bin/threadfuzz midgcmicrotask 1 10: 3/3 independent focused runs, 0 failures.

The #531 candidate changes only Object creation-order storage to borrow immutable Context-arena Shape name slices and teaches Object/GC teardown not to free borrowed entries. It does not modify Thread, Worker, Promise, microtask, task, or queue code. With that candidate applied:

  • zig-out/bin/threadfuzz midgcmicrotask 1 10: 2/2 failures;
  • both fail with TypeError: Thread constructor requires a callable argument;
  • a sequential 400-seed mid-GC profile was stopped after the same failure appeared at seeds 10 and 46; the gate was already failed and its observed rate would have consumed hours;
  • full units were otherwise 1,634 pass / 1 skip / 0 fail / 0 leak, and suppression-free TSan named-deletion/relocation was 5/5 clean.

The candidate materially changes allocation count and execution timing, so this appears to expose a real Thread/job lifecycle ordering defect rather than directly touching that subsystem. I have not landed the candidate and have not changed the separately owned queue/job implementation. The exact focused reproducer is:

zig-out/bin/threadfuzz midgcmicrotask 1 10

Related: #531. The candidate remains held until this differential is understood and the no-workaround fuzz gate is green.

9 days ago

Current main CI run 32435239065 exposed a functional no-GIL regression in shard 1/4: cve/mc-lock-stop-vs-park.js expected 400 but observed 401 after 465 ms. The diagnostic says all six Thread records were done/exited, none threw, and every join was settled. The shard otherwise passed 62/63 cases. This is independent of the #670 tooling-only commit and is left to the user-owned Thread/job lifecycle workstream; I made no changes to that surface.

9 days ago

Validation evidence from unrelated #707 work: the first 6-shard full-unit run intermittently segfaulted in C-API: worker inspector marshals commands and callbacks across threads at src/c_api.zig:18497 (inspectorStatementBoundary, writing state.operation_depth) from src/worker.zig:1161 (workerMain). The exact isolated test then passed 1/1 in 126 ms, and a complete 6-shard retry passed 1,805 / skipped 1 / failed 0 / leaked 0. The #707 diff touched only compiler/context/VM lowering and its compiler tests had already passed in the failing shard, so this is useful evidence for an intermittent worker-inspector state-lifetime race rather than a #707 regression.

9 days ago

The historical #531 borrowed-Shape-key blocker recorded in the seed-10 report is resolved on current main by the intervening lifecycle work. The candidate is now landed as 88cde689.

Revalidation on the exact final candidate:

  • full midgc seed-10 profile: 45/45, 0 failures;
  • focused midgcmicrotask 1 10: 3/3 independent runs;
  • suppression-free TSan full seed-10 profile: 45/45, 0 failures/races/leaks;
  • lifecycle seed-1 profile: 56/56;
  • default/amplified/broad/verify threadfuzz profiles: all green.

This closes only the historical dependency from #531; #482 remains open for its own broader Thread/Worker/channel/join lifecycle acceptance.

9 days ago

Structured-clone message graphs are hardened by completed child #859: fix(structured-clone): secure object memoization (#859). Worker/postMessage wire ordering and ownership are unchanged; threadfuzz completed 400 programs with 0 failures, and Home private serialization fixtures passed in Debug, ReleaseSafe, and TSan.

9 days ago

Related hardening landed: #873 securely seeds the exact-thread-ID ThreadLocal value index, preserves lifecycle/GC semantics, and is implemented by 3cf4429e.

Sign in to comment on this issue.