ReviewOS

zig-utils/zig-js

Promises: bound host rejection queue draining

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

Parent: #493 Related: #474, #480, #482, #495, #884, #885, #888

Problem

The realm-local unhandled- and handled-rejection queues are growable arrays, and each host checkpoint removes index zero with orderedRemove(0). The private ABI drains those functions in a loop, so N JavaScript-created rejection notifications copy Θ(N²) Promise pointers while repeatedly holding realm_lock. Rejection fanout is script-controlled, making this an avoidable latency and contention surface.

The same growable-array append is the fallible publication boundary tracked by #884. A cursor would fix front shifting but retain that boundary; the final design should solve both issues without adding a second queue representation.

Required implementation

  • Replace array front-removal with one allocation-free realm-owned FIFO: Context head/tail roots plus a single intrusive successor per queued Promise.
  • Keep append and dequeue O(1), clear every removed successor immediately, and reset head/tail/length exactly at exhaustion.
  • Enforce single membership: a Promise may enter the handled queue only after leaving the unhandled queue, and may never be linked twice.
  • Trace every pending intrusive node under realm_lock; relocate both Context endpoints and every Promise successor while mutators are stopped.
  • Preserve early-handled skipping, one-time notification state, and reentrant ordering: callbacks may enqueue more notifications, which follow the pending suffix and drain in the same checkpoint.
  • Keep Promise in its existing 256-byte GC allocation class.
  • Do not reverse events, use unordered removal, cap valid fanout, defer required notifications, weaken locking, add an emergency allocation pool, or recognize particular workloads.

Acceptance

  • A deterministic 4,096-entry witness proves allocation-free O(1) FIFO append/pop, length-bounded iteration, cleared links, reentrant tail insertion, exact exhaustion reset, and reuse.
  • Unhandled and later-handled notifications retain FIFO order, early-handled entries are skipped, and reentrant enqueues follow pending entries.
  • Precise tracing and moving relocation cover all pending nodes and rewrite both endpoints/successors without retaining consumed nodes.
  • Promise allocation density, empty/repeated behavior, teardown, and publication invariants remain exact.
  • Focused Promise/private-ABI tests pass in Debug, ReleaseSafe, and suppression-free TSan; relevant threadfuzz and the full unit suite have zero failures/leaks.
  • Exact affected Test262 Promise coverage has zero regressions; documentation states the allocation-free FIFO contract without unsupported throughput claims.
11 days ago

Implementation evidence so far:

  • The 4,096-entry queue witness passes and proves O(1) cursor advancement, unchanged backing storage during partial drain, FIFO reentrant append, exact exhaustion reset, capacity reuse, empty behavior, and append OOM invariants.
  • The private rejection-dispatch witness passes a 64-entry unhandled FIFO plus a reentrant 65th rejection, followed by 64 ordered handled notifications.
  • Promise/rejection focused tests pass in Debug and ReleaseSafe; the rejection filter passes under suppression-free TSan.
  • Moving-GC relocation proves consumed prefixes are untouched while pending suffixes are rewritten; tracing uses the same pending suffix.
  • The pinned Home private ABI passes 392/392 in Debug, ReleaseSafe, and TSan after an audit of 538 symbols (447 implemented, zero pending or unclassified).
  • Relevant default threadfuzz passes 400/400. Documentation builds 50 pages and validates 328 links; the manifest change is limited to the updated Home integration page and tree hash.

The full lifecycle threadfuzz profile is currently running because it exposed the independent test-harness race tracked in #875. The full unit suite and exact Promise Test262 coverage remain intentionally unchecked until completed.

11 days ago

Closing as implemented and accepted. The final allocation-free intrusive FIFO is part of 4a1e5d82; its rendered Home integration manifest was reconciled by 09279120. No user-owned #874 stash was read, applied, or modified for this closure.

Final evidence:

  • The 4,096-entry FIFO witness passes in Debug, ReleaseSafe, and TSan. It covers O(1) append/pop, length-bounded traversal, cleared links, reentrant tail insertion after the pending suffix, exact empty reset, and reuse.
  • The tracker witness proves unhandled publication, repeated settlement, early handling, and later-handled publication perform zero allocator calls while flags and queue membership commit exactly.
  • Single membership is enforced by the overlaid rejection link and atomic linked bit; dequeue clears the successor before the Promise may enter the handled queue.
  • Precise GC traces both endpoints and every length-bounded node under realm_lock; relocation rewrites endpoints and linked successors. Teardown unlinks both queues before heap destruction, and consumed nodes are not retained.
  • Promise remains in the 256-byte production GC allocation class. The private rejection dispatch preserves exact Promise/reason identities and reentrant event order.
  • Fresh standalone ReleaseSafe rejection coverage: 13/13. Focused Debug/TSan queue and tracker witnesses pass, and Home private ABI links/runs 392/392 shims.
  • Linux integration run 34287554681 passed full Debug/TSan units, all Debug/ReleaseSafe/TSan no-GIL corpus shards, and every configured threadfuzz/TSan-fuzz profile. Its sole unrelated failure was the #903 detached-buffer reference expectation.
  • Fresh exact-parent Promise Test262 from the implementation boundary: 677/677 -> 677/677, 0 flips and 0 regressions.
  • docs/HOME_INTEGRATION.md states the allocation-free intrusive FIFO contract without publishing a throughput claim; the pinned BunPress build and all 328 links passed.

The quadratic front removal and its fallible queue-publication boundary are both gone; no cursor compatibility path, event cap, emergency pool, or second queue representation remains.

Sign in to comment on this issue.