ReviewOS

zig-utils/zig-js

GC: defer blocked heap-cap recovery to the next safe boundary

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

Parents: #100, #628 Related: #649

Problem

BudgetAllocator.tryRecover correctly refuses synchronous collection while gc_runtime.allocationRecoveryBlocked() is true, because object/property/element backing allocation can hold trace-sensitive engine locks. It then returns OOM without retaining any recovery request. JavaScript can catch that OOM, release a large live graph, and retry at a safe statement boundary, but no collection is pending and the retry fails against the unchanged cap.

The promoted no-GIL case semantics/oom-one-thread.js proves this exact sequence. The hog catches pressure OOM, clears its ~192 MiB hoard, then its small recovery allocation escapes OOM. Allocation-free runner evidence from #649 shows id 1 threw while ids 2-4 completed normally; budget use remains 201326577 / 201326592 bytes.

Required implementation

  • Extend the budget-recovery contract with an allocation-free deferred callback.
  • When synchronous recovery is blocked, request Context GC atomically and still fail the current allocation closed.
  • Service that request only at an existing safe interpreter boundary.
  • Preserve the recursion guard and never invoke the collector under an allocator-internal or trace-sensitive lock.
  • Do not raise the heap cap, add retries in JavaScript, or weaken the corpus witness.

Acceptance

  • Unit coverage proves blocked allocation calls only the deferred callback, not the recovery callback, and a later safe allocation can recover.
  • The unchanged semantics/oom-one-thread.js passes in no-GIL Debug and ReleaseSafe with the 192 MiB cap.
  • Existing trace-sensitive-lock and parallel allocation-recovery tests remain green under TSan.
  • Full unit/TSan shards and no-GIL corpus gates have no new regressions.
9 days ago

Implemented in 2da6f4bb. A quota miss under a trace-sensitive allocator/GC lock is now recorded atomically, then serviced at the shared tree-walker/VM throw-to-catch unwind after locks release and while roots remain published. The exact semantics/oom-one-thread.js witness changed fail -> pass in Debug (8.15 s), ReleaseSafe (440 ms), and TSan (60.2 s); focused normal/TSan tests passed with 0 races/leaks. The full unit retry passed 1,745, skipped 1, failed 0, leaked 0 across all 1,746 linked tests. Fresh Linux validation: CI run 32369900898.

9 days ago

Fresh Linux ReleaseSafe no-GIL evidence is now available: run 32369900898, shard 3/4 passed semantics/oom-one-thread.js. The shard reached 61/62; its only failure was the independent managed-string invariant crash now tracked in #651. This confirms the deferred recovery fix on the CI platform/build mode that originally exposed the OOM regression.

9 days ago

Closing with fresh Linux ReleaseSafe no-GIL evidence: semantics/oom-one-thread.js passed unchanged in run 32369900898, shard 3/4, confirming deferred cap recovery at the originally failing platform/build boundary. The later independent managed-string failure is #651.

Sign in to comment on this issue.