ReviewOS

zig-utils/zig-js

Runtime: make heap-limit allocation failures catchable at JS allocation sites

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

Parent: #24, #11.

The PR-249 semantics/oom-one-thread.js witness now exercises zig-js's real Context.Options.heap_limit_bytes cap instead of relying on the vendored JSC RAM-cap directive. That makes the remaining failure shape much sharper: the cap fires, but the allocation pressure escapes as a whole-Thread completion OutOfMemoryError instead of being caught by the allocating JS frame's local try/catch.

Current probe evidence:

FAIL semantics/oom-one-thread.js: OutOfMemoryError: Context heap limit exceeded

This is different from the already-landed reserved thread OOM completion object: that path is correct for thread startup/completion pressure where JS execution cannot publish a fresh exception object. Here, the allocation happens inside running JS, so the ideal contract is a catchable JS exception at the allocation site.

Acceptance:

  • Allocations that exceed Context.Options.heap_limit_bytes inside an active JS frame throw OutOfMemoryError into that frame.
  • A local JS try/catch around the allocating expression can observe the OutOfMemoryError before it becomes the whole Thread completion result.
  • Host entry pressure before a JS frame exists may still surface as Zig error.OutOfMemory / top-level evaluation failure where appropriate.
  • semantics/oom-one-thread.js passes or progresses past the allocation-site catchability blocker.
  • Existing thread OOM survivor/recovery tests stay green, including sibling join behavior after one thread hits the heap cap.
9 days ago

Seeded by f3cb0cc: https://github.com/zig-utils/zig-js/commit/f3cb0cc01d039502dbf325303e4c0ada86f28f2e

That commit makes the PR-249 semantics/oom-one-thread.js probe use zig-js's actual heap cap and records the current blocker evidence in the audit/docs. The probe now proves the cap fires; this issue tracks changing the runtime exception path so the allocating JS frame can catch the OOM locally.

9 days ago

Landed a partial #27 improvement in 75a7e49: https://github.com/zig-utils/zig-js/commit/75a7e4980b5ad435dedeb7f646945950e0a81c31

What changed:

  • Context now passes its prebuilt heap-limit OutOfMemoryError object into each Interpreter.
  • The tree-walker try path and VM handler unwinding can reinterpret capped error.OutOfMemory as catchable error.Throw when a JS handler is already active.
  • If no JS handler is active, host/top-level capped allocation failure still propagates as error.OutOfMemory.
  • Added a focused regression proving new ArrayBuffer(1 << 20) pressure under heap_limit_bytes can be caught by a JS catch {}.

Validation:

  • /Users/chris/.local/bin/zig build test -Dtest-filter='heap_limit_bytes allocation pressure' --summary all
  • /Users/chris/.local/bin/zig build test -Dtest-filter='heap_limit_bytes lets sibling Threads survive one Thread OOM' --summary all
  • /Users/chris/.local/bin/zig build threads-test -Dthreads-case='semantics/oom-one-thread.js' --summary all still fails with the documented blocker evidence:
FAIL semantics/oom-one-thread.js: OutOfMemoryError: Context heap limit exceeded

So this issue is not done yet. The remaining PR-249 blocker appears sharper now: the witness uses catch (e) plus many small array-element allocations, which can still exhaust the heap before the catch binding/body can run, causing the whole Thread completion path to publish the reserved OOM object.

While testing, I also found a separate aborting string allocation path (strcell.makeCell OOM) and opened #28 for that.

9 days ago

Follow-up from #28 landed in e99dec7: https://github.com/zig-utils/zig-js/commit/e99dec7510233a1a63ccc8a072eda4101092d0c8

This removes the aborting string-concat OOM path found while probing #27: string doubling under heap_limit_bytes now reaches JS catch/throw behavior instead of panicking in strcell.makeCell. The broader PR-249 semantics/oom-one-thread.js blocker remains tracked here.

9 days ago

Landed another #27 progress slice 4a83575: https://github.com/zig-utils/zig-js/commit/4a8357525e283a2cbba2bcff2090fa37319e0869

What changed:

  • For capped contexts with a reserved OutOfMemoryError, try { ... } catch (e) { ... } now pre-reserves the simple catch environment/binding before entering the allocation-pressure region.
  • This lets a converted heap-limit error.OutOfMemory land in a local catch binding without needing fresh binding-table allocation at the cap boundary.
  • Added a regression for heap-limit pressure caught via catch (e) that verifies the observed value is the reserved OutOfMemoryError.

Checks run:

  • zig build test -Dtest-filter='heap_limit_bytes allocation pressure is catchable with catch binding' --summary all
  • zig build test -Dtest-filter='heap_limit_bytes allocation pressure is catchable inside JS try' --summary all
  • zig build test -Dtest-filter='heap_limit_bytes lets sibling Threads survive one Thread OOM' --summary all
  • zig build threads-test -Dthreads-case='semantics/oom-one-thread.js' --summary all still fails with:
FAIL semantics/oom-one-thread.js: OutOfMemoryError: Context heap limit exceeded

So #27 is not closed. This removes the simple catch-binding allocation blocker; the remaining witness failure likely involves the larger PR-249 recovery contract under arena-backed heap caps (the hog releases JS references, but the arena-backed budget does not reclaim individual allocations without a GC/recovery path).

9 days ago

Follow-up issue opened for the remaining recovery blocker: #29.

The current #27 state after 4a83575: focused allocation-site catchability is improved, including catch (e) under heap pressure. The PR-249 witness still fails, and local probes point at heap-cap collect/retry/recovery rather than simple catch binding. Keeping #27 open until either #29 lands or the witness progresses enough to separate catchability from recovery conclusively.

9 days ago

Small #27/#29 update from b557f38: the reference probe now runs heap-limit cases with GC enabled. The failure evidence is unchanged (), which means the remaining #27 catchability failure is no longer confounded by an arena-backed cap that cannot reclaim. It is now tied to #29's active-thread collect/retry recovery contract.

9 days ago

Clean correction for the prior shell-mangled note: b557f38 makes the semantics/oom-one-thread.js reference probe run heap-limit cases with GC enabled. The failure evidence is unchanged: OutOfMemoryError: Context heap limit exceeded. That means the remaining #27 catchability failure is no longer confounded by an arena-backed cap that cannot reclaim; it is now tied to #29 active-thread collect/retry recovery.

9 days ago

Resolved by the heap-limit series through 78b553a.

The original blocker was that semantics/oom-one-thread.js surfaced OutOfMemoryError as the whole Thread completion instead of letting the allocating frame recover. After the catch-binding fixes plus the #29 collect/retry work, the witness now passes and is promoted into conformance/threads_test.zig.

Focused validation included:

  • zig build test with filter heap_limit_bytes allocation pressure
  • zig build test with filter Context heap_limit_bytes lets sibling Threads survive one Thread OOM
  • zig build threads-test -Dthreads-case=semantics/oom-one-thread.js --summary all
  • zig build threads-reference-probes --summary all

Remaining broader emergency recovery work is tracked in #30, not here.

Sign in to comment on this issue.