ReviewOS

zig-utils/zig-js

GC: defer C-API class finalizers outside collector locks

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

Parent: #140 Tracked by: #134 Related: #325, #324 Dependency: zig-utils/zig-gc#34

Problem

The object-cell finalizer currently invokes CApiObjectOwner.finishOnce directly from Binding.finalize. In parallel collection, zig-gc runs that finalizer while holding alloc_lock. A class finalizer is embedder code and may release handles, allocate, or re-enter the VM, so direct invocation can deadlock or observe a half-restored collector state.

Scope

  • Queue C-API class-owner finalization without allocating from the sweep path.
  • Drain exact-once callbacks from Binding.afterSweep, after collector locks are released and publication is restored by zig-utils/zig-gc#34.
  • Drain teardown callbacks only after heap deinit and after the Context has made the heap unavailable.
  • Preserve class refcounts, prototype ownership, cross-context rejection, no-GIL ordering, and current arena-mode teardown behavior.
  • Keep external buffer/string release records separate unless one generic intrusive owner queue measurably simplifies both without weakening type contracts.

Acceptance

  • Focused tests prove ordinary GC and context teardown invoke once, duplicate references do not duplicate callbacks, and construction failure releases correctly.
  • A callback re-enters representative C API operations without deadlock and observes a fully restored heap state.
  • Parallel/no-GIL and TSan coverage is green.
  • One full CI matrix is accepted and parent trackers are updated.
9 days ago

66fa951b establishes and validates the generic post-sweep queue for external ArrayBuffer owners, including callback reentry. This issue remains intentionally open for CApiObjectOwner.finishOnce: custom JSClass finalizers still execute directly from object finalization and need migration plus ordering/reentry tests rather than being folded silently into #324.

9 days ago

Implemented on main in 983cb3bc.

Object-cell finalization now publishes stable CApiObjectOwner records to a dedicated allocation-free intrusive queue. Binding.afterSweep drains class callbacks only after zig-gc releases collector locks and restores allocation publication; heap teardown drains after gc is made unavailable. The existing exact-once guard still owns class-chain callbacks, custom-accessor cleanup, and class refcounts, including construction-failure and arena-teardown paths.

The focused witness proves ordinary GC callback delivery, nested allocation plus JSGarbageCollect reentry, protected-value survival through the nested cycle, duplicate protects without duplicate finalization, and one teardown callback. It passes with 3/3 tests and no leaks. Design documentation is updated in docs/threads/P7-gc-design.md.

Acceptance matrix: https://github.com/zig-utils/zig-js/actions/runs/29702521425. Leaving open for the full matrix, including no-GIL/TSan coverage.

9 days ago

Accepted by descendant CI run 29703497726: 39 jobs passed, with only the intentional nightly skip. Deferred C-API finalizers, reentrancy coverage, and the full collector matrix are green.

Sign in to comment on this issue.