ReviewOS

zig-utils/zig-js

Threadfuzz: keep Worker lifecycle cases live under bounded slots

#1039
Closed chris opened this 9 days ago ·
9 days ago

Parent: #502 Found by: hosted CI for bc2f22e

Verified failure

The runtime coordinator bounds runnable engine threads to logical_cpu_count - 1 under the automatic policy. Three threadfuzz lifecycle programs can occupy every available slot in a nonblocking JavaScript loop while waiting for a queued peer that needs a slot:

  • lifecycle seed 1 worker-thread-overlap exceeded 120,000 ms in job 113126071045;
  • lifecycle seed 1 module-worker-terminate-finalization under unsuppressed TSan exceeded 120,000 ms in job 113126071014;
  • midgc seed 2 worker-terminate-finalization exceeded 300,000 ms in job 113126071057.

All three then failed the 30-second teardown grace because these lifecycle helpers do not publish their Context to the watchdog, so the watchdog reported no active context was available to interrupt.

The common liveness defect is in the generated stress program: a Thread busy-spins waiting for Workers, or a Worker loops forever doing atomic RMW while foreground Threads are still queued. Weighted admission cannot preempt a runnable thread, so a small host can reach a valid scheduler state with no thread capable of releasing a slot.

Scope

  • Add a focused lifecycle-case reproduction that can force one runnable slot without depending on host CPU count.
  • Make Worker/Thread overlap waits enter the engine's blocking boundary instead of consuming a runnable slot while waiting for a queued peer.
  • Preserve the termination/finalization workload: Workers must remain live until explicit terminate, and script and module Worker variants must retain their exact cleanup and asyncJoin oracles.
  • Publish the active Context to the existing per-case watchdog for lifecycle and mid-GC cases so a future unrelated hang can request cooperative termination and complete bounded teardown.
  • Audit adjacent Worker lifecycle generators for the same all-slots-busy dependency.

Definition of done

  • One-slot focused reproductions pass for worker-thread-overlap, worker-terminate-finalization, and module-worker-terminate-finalization.
  • Normal lifecycle and midgc profiles pass without a timeout.
  • Unsuppressed TSan focused coverage passes.
  • The full unit gate passes with no leak.
  • #502 records the landed commit and validation.

Guardrails

Do not raise watchdog deadlines, disable the bounded scheduler, reduce the generated concurrency, or treat timeout exit 124/125 as success.

9 days ago

Landed on main in 75395544.

The lifecycle generators now park through coordinator-aware waits whenever they depend on a queued Worker or shared-realm Thread. Termination Workers still increment their progress lane before each bounded park and remain live until explicit terminate(). The affected lifecycle Contexts are armed in the existing watchdog, and boundedlifecycle forces one runnable slot across the seven audited termination/overlap families.

Validation:

  • one-slot normal: 140/140 programs pass across seeds 1–20
  • one-slot unsuppressed TSan: 35/35 programs pass across seeds 1–5
  • lifecycle normal: 560/560 programs pass across seeds 1–10
  • lifecycle unsuppressed TSan: 56/56 programs pass at seed 1
  • midgc normal: 90/90 programs pass across hosted seeds 1–2
  • full unit suite on the same engine revision: 2,581 passed; 1 skipped; 0 failed; 0 leaked
  • docs link check: 378 files pass; pinned BunPress manifest check passes
  • test262 flips: 0; no engine semantics changed

Sign in to comment on this issue.