ReviewOS

zig-utils/zig-js

Memory: enforce JSON stringify heap-budget peak contract in CI

#828
Closed chris opened this 11 days ago ·
11 days ago

Parent: #480. Related: #473, #722.

Current main 8e1e441d2f96867efc40da6cafcc6fbeed0f5476 fails the Linux Debug unit shard 2/4: context.test.heap_limit_bytes: JSON stringify bulk string runs fail atomically, at stats.peak_bytes <= stats.limit_bytes (src/context.zig:12280). Job result: 489 passed, 54 platform skips, 1 failed, 0 leaked; Zig 0.17.0-dev.1818 on the runner. The preceding 2182-pass/1-skip full local macOS Debug run did not reproduce it.

The first JavaScript atomic-failure assertion passed; the failing assertion is the peak-memory contract. BudgetAllocator currently permits a one-time grace after a recovery collection, and reports original limit_bytes separately from grace_granted_bytes. That is a source-level lead, not yet a reproduced root cause.

Acceptance:

  • Reproduce with allocator telemetry on the failing platform.
  • Reconcile/enforce the documented hard-limit contract without simply raising a cap, weakening the assertion, disabling collection, or hiding the failure.
  • Preserve atomic stringify publication and callback ordering.
  • Run focused OOM, allocator recovery, no-GIL/TSan and full unit coverage.

Keep #823/#824 open until terminal CI verification is resolved.

11 days ago

The Linux TSan unit shard 2/4 also failed the same peak_bytes <= limit_bytes assertion at src/context.zig:12280, not a reported TSan race: https://github.com/zig-utils/zig-js/actions/runs/33219707340/job/99011041278 . Both complete job logs are retained locally. This reinforces that the budget contract needs reconciliation rather than suppressing a sanitizer report.

11 days ago

Implemented on main by f4a6a8cd — fix(memory): keep heap recovery within hard cap (#828).

The root inconsistency was #722's post-collection grace: production contexts raised the allocator ceiling above heap_limit_bytes while HeapBudgetStats.limit_bytes and the public contract continued to describe the original hard cap. The fix replaces that allowance with a proportional one-shot recovery reserve allocated and charged inside the cap at Context creation. A recovery collection that remains pinned can release those already-counted bytes so the mutator unwinds, but used_bytes and lifetime peak_bytes never exceed limit_bytes. Concurrent no-GIL release is exactly once; peers wait only for the infallible allocator free, with bounded yielding. Stats now report configured and released recovery-reserve bytes.

Local verification on the committed snapshot:

  • allocator contract: 7/7 Debug and 7/7 unsuppressed TSan, including four simultaneous reserve consumers
  • exact JSON atomic/peak witnesses: 2/2
  • full heap_limit_bytes matrix: 21/21 Debug and 21/21 unsuppressed TSan
  • #722 semantics/oom-one-thread.js: 1/1 GIL and 1/1 no-GIL
  • full units: 2,220 passed, 1 skipped, 0 failed, 0 leaked

The previous failing platform was Linux, while these runs are macOS. Pushed-revision CI is the terminal Linux verification gate. I am keeping the issue open until that run completes rather than treating a local pass as Linux reproduction.

11 days ago

Terminal Linux verification is now complete on descendant be776fef. All four Debug unit shards and all four TSan unit shards passed. In particular, the previously failing shard 2 passed in both configurations:

The overall workflow conclusion was caused by separate no-GIL corpus timeouts; the heap-budget/JSON peak contract gate itself is green on Linux. Closing against f4a6a8cd.

Sign in to comment on this issue.