ReviewOS

zig-utils/zig-js

GC: keep Promise roots live during parallel publication

#82
Closed chris opened this 11 days ago ·
11 days ago

Parent: #14 Related: #62

Failure

The Linux threadfuzz-midgc job crashes in the promise-publication case on successive main runs, including 29339154198.

Command:

THREADFUZZ_SEED_TIMEOUT_MS=300000 zig build threadfuzz -Dfuzz-midgc=true -Dfuzz-iters=20

Crash:

General protection exception (no address available)
src/gc.zig:69 in markManaged
    if (cell.gc_owned) v.mark(cell);
src/gc.zig:613 in publishInterpreterRoots
src/context.zig:3035 in publishParallelRoots
src/context.zig:3008 in serviceParallelGc
...
conformance/threadfuzz.zig:16710 in runMidScriptPromisePublicationGc

Line 69 is the Promise branch, so an owner-thread root-publication scan is dereferencing a stale or otherwise invalid *Promise before it can route the root through the heap's tolerant membership/barrier validation. Promise pointers can come from reaction results and current/pending microtasks; the exact container still needs deterministic attribution.

Scope

  • reproduce and identify the precise promise root container/lifecycle window;
  • keep every popped/current/batched microtask or reaction result strongly rooted until execution/transfer completes;
  • prevent root publication from dereferencing unvalidated stale pointers, without masking a lost-root semantic bug;
  • preserve arena-owned Promise records and GC-owned Promise cells;
  • retain fail-closed parallel collection behavior and existing lock order.

Acceptance criteria

  • Focused seed/replay identifies the exact dangling root source.
  • A regression witness fails before the fix and survives repeated mid-script parallel collections after it.
  • Promise settlement, asyncJoin, thenable adoption, local/realm queue transfer, and teardown results remain exact.
  • Focused midgc fuzzer passes at and above the failing depth.
  • TSan midgc and the final batched CI gate pass.
  • Commit references this issue and is pushed as Chris without coauthors.
11 days ago

Root cause fixed and pushed to main in c66021b.

A Microtask is a tagged union in practice. Reaction jobs initialize reaction and argument; thenable jobs intentionally set reaction = undefined and initialize thenable, then_fn, and promise. The GC tracer previously traced every field regardless of kind. During parallel root publication, bytes in the inactive reaction.result field could look like a Promise pointer, producing the CI gc_owned dereference crash. This was not a lost live Promise.

The tracer now switches on Microtask.kind and follows only active fields. A direct regression poisons every inactive field with valid decoy roots and verifies neither variant marks the other variants fields.

Validation:

  • focused regression: 2/2 passed, zero leaked
  • exact CI mid-GC command: 900 programs from seed 1, zero failures, wall time 394611 ms
  • formatting and diff checks clean

The preceding full main validation for the adjacent #81 slice also passed 758/758 with zero leaks; this isolated GC change was then exercised with the substantially heavier exact failing fuzz gate.

11 days ago

Completed by c66021b on main.

Sign in to comment on this issue.