Parent: #474 Related: #480, #493, #884
Problem
Promise reaction scheduling has fallible state changes on both sides of settlement:
settlecommits the Promise state and moves every reaction out of the Promise before appending reaction jobs to the microtask queue. If one append returnsOutOfMemory, already-appended jobs remain while the current and remaining reactions are discarded by cleanup. Native resolving functions have already set their shared once-only bit, so the exact settlement cannot be retried.performThenReactionsmarks a rejected source handled and, for a late handler, publishesrejectionHandledbefore appending the already-settled reaction job. If that append fails, the handler never runs even though tracker state says handling succeeded.
Bounded-heap recovery makes this observable after the current evaluation aborts: dependent Promises may remain pending forever, some sibling reactions may run while later ones disappear, and a rejected source may lose its unhandled status without an installed handler.
#884 removes the allocator boundary from host rejection-tracker publication. This issue owns the distinct Promise-to-microtask scheduling transaction.
Required implementation
- Establish an exact transaction between Promise state/reaction ownership and all required microtask queue publications.
- Before consuming a once-only resolving capability or committing a settled-source handler, guarantee capacity for every job that must become observable, or use an allocation-free scheduling representation with proved ownership.
- Preserve FIFO ordering across existing jobs, multiple reactions, thenable assimilation, async/generator continuations, shared-realm no-GIL producers, and reentrant microtask checkpoints.
- Keep the canonical queue/Promise lock order explicit and compatible with concurrent marking, teardown, and bounded allocator recovery.
- Do not partially enqueue a settlement batch, discard unscheduled reactions, reset once-only state for an unsafe retry, globally serialize JavaScript, cap reaction fanout, or swallow OOM.
Acceptance
- Deterministic allocator-fault witnesses cover failure before the first job, between sibling jobs, and during an already-settled
.then. - After every injected failure, Promise state, reaction ownership, dependent Promise state, rejection-tracker flags, and microtask membership agree.
- Recovery delivers every successfully committed reaction exactly once in registration order, with no permanently pending dependent Promise caused by lost scheduling.
- No-GIL concurrent attachment/settlement cannot deadlock, duplicate, reorder, or lose jobs.
- Moving/concurrent GC, bounded-heap recovery, async/generator paths, private ABI, threadfuzz, full units, and suppression-free TSan pass.
- Exact affected Test262 Promise/async coverage has zero regressions and documentation makes no unsupported OOM guarantee.
Security boundary
Reaction fanout and allocator pressure are script-controlled. OOM must fail closed at a transaction boundary rather than leave partially scheduled engine state that can be observed after recovery.