Skip to content

Runtime: extend heap-cap emergency recovery beyond current safe allocation classes #30

Description

@chrisbbreuer

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 Runtime: make no-GIL heap-cap recovery safepoint-owned #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.
  • 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 (Context heap_limit_bytes object side-store pressure recovers garbage).
  • 72beb646 blocks the generic budget-recovery callback inside GcCellBacking allocator-internal critical sections, so GC-cell chunk OOM fails outward to zig-gc's safe outer Heap.create recovery path instead of self-deadlocking while backing locks are held.
  • b3c86930 roots 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 reserved OutOfMemoryError values into JS catch (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 emergency-recovery slice, while keeping collection fail-closed when peers could hold unscanned roots or when the current allocation site holds a trace-sensitive lock.

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.

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 or other trace-sensitive locks are held, while GC tracing may need those same locks.

Remaining perfection work

  • Extend abort-safe no-GIL / parallel_js emergency recovery beyond the current safepoint-owned GC-cell path, without scanning unpublishing peers or weakening the fail-closed policy.
  • 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 the tracer needs.
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions