ReviewOS

zig-utils/zig-js

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

#828
Closed chris opened this 9 days ago ·
9 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.

9 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.

9 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.

9 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.