Follow-up from #29 after zig-gc b99de49, zig-js 78b553a, and zig-js 45a58ce promoted semantics/oom-one-thread.js and added ArrayBuffer byte-slab recovery.
Current status
The landed recovery paths cover these GC-backed capped-context allocation failures:
- GC cell slab allocation failures retry after
Context.collectForAllocationFailure()when the current thread can safely collect. - no-GIL GC-cell slab recovery has a safepoint-owned current-interpreter path from #31; it reuses the abort-safe parallel root-publication collector and retries only after a collection actually sweeps.
- GC-backed ArrayBuffer byte allocation failures retry once after the same recovery hook, including a live no-GIL peer witness for safepoint-owned byte-slab pressure.
- Budgeted runtime allocations now have a guarded one-shot recovery hook (
2d4d3ae0): when the heap cap would be exceeded, the quota allocator can collect safely, reserve again, and continue without scattering ad-hoc retries through object/property code. - GC-backed object side-store garbage can now be reclaimed by allocation-failure recovery under the heap cap in safe single-threaded/GIL-capable contexts (
Context heap_limit_bytes object side-store pressure recovers garbage). 72beb646blocks the generic budget-recovery callback insideGcCellBackingallocator-internal critical sections, so GC-cell chunk OOM fails outward to zig-gc's safe outerHeap.createrecovery path instead of self-deadlocking while backing locks are held.b3c86930roots pre-reserved heap-limit catch environments in the interpreter's precise environment root stack, so emergency recovery cannot collect a catch scope needed to convert reservedOutOfMemoryErrorvalues into JScatch (e)bindings.- Single-threaded and GIL-mode contexts can run emergency collection safely when peers are parked/published under the supported scan policy.
- Arena-backed heap caps remain fail-closed and non-reclaimable by design.
That is enough for the PR-249 OOM recovery witness, GC-backed ArrayBuffer byte pressure, ordinary budgeted GC-backed side-store pressure, and the current no-GIL GC-cell / ArrayBuffer emergency-recovery slices, while keeping collection fail-closed when peers could hold unscanned roots, when the current allocation site holds a trace-sensitive lock, or when deferred generator/iterator cells prevent a safe parallel finish.
Side-store policy covered so far
Object side-store pressure is now covered by focused tests:
- dropped object side stores are reclaimable by explicit collection, and a second allocation wave can then succeed under the same GC-backed cap;
- allocation-failure recovery now also reclaims dropped side stores automatically in safe single-threaded/GIL-capable contexts;
- live side-store pressure still fails closed as host
error.OutOfMemory, proving the cap is enforced when collection cannot free enough budget; - live no-GIL trace-sensitive lock sites are pinned by regression coverage so the runtime does not collect/retry while object/environment/promise/async-generator request or allocator-internal locks could deadlock with tracing.
The recovery hook is intentionally guarded, recursion-safe, and disabled inside allocator-internal backing locks rather than a blanket retry. Many no-GIL side-store allocations can happen while object property/element, promise, environment, async-generator request, iterator/generator, or other trace-sensitive state is held, while GC tracing may need those same structures.
Deferred generator / iterator policy
#36 now tracks the deferred generator/iterator seam separately because it is not just an ordinary trace-sensitive-lock problem. The current fail-closed witnesses cover:
- suspended generator execution storage;
- resumable generator handler buffers;
- pending async-generator request buffers;
- iterator-helper backing state;
- telemetry that splits deferred-cell aborts into
gc_par_deferred_aborts/def-abortwithout weakening the round-limit abort accounting identity.
No positive recoverable generator/iterator class has been proven. #36 is closed with the conservative policy that all current deferred generator/iterator side-store pressure fails closed while those edges remain pending, unless a future site can prove the collector can safely sweep through a sound world-stopped path.
Remaining perfection work
- Extend abort-safe no-GIL /
parallel_jsemergency recovery only for allocation classes that can prove the current interpreter can collect without scanning unpublishing peers, holding trace-sensitive locks, or leaving deferred generator/iterator edges unresolved. - Define finer lock-aware no-GIL collect/retry policies for side stores and other non-cell allocations where the allocation site can prove it does not hold locks or deferred state the tracer needs.
- Keep the closed #36 policy documented: deferred generator/iterator side-store pressure is intentionally all-fail-closed unless a future allocation site proves a sound world-stopped recovery path.
- Add focused tests for every newly recoverable allocation class; keep fail-closed witnesses for allocation classes that remain unsafe to recover generically.
- Keep arena-backed heap caps fail-closed unless a caller explicitly opts into GC-backed recovery.
This should stay separate from #29/#24: the promoted PR-249 witness is covered, but capped allocation recovery is not yet perfect for every allocation class or no-GIL interleaving.