Merge train: #9564 #9565 (mysql2 fallback; cross-thread promise pinning) - #9569
Merged
Conversation
added 3 commits
September 2, 2026 21:31
…ey settle (#9552) A promise minted for a cross-thread settlement — every stdlib fetch/db/ws request, `spawn`, `Atomics.waitAsync` — leaves the runtime as a bare usize inside a worker future and is invisible to every root scanner until its completion is queued back. Nothing on the JS side points AT it either: the awaiting continuation hangs OFF it (`P.on_fulfilled`) and `P.next` is an edge out. The pin was the caller's job; `spawn` and `waitAsync` took it, ~110 stdlib sites (fetch among them) never did. A full collection landing in the in-flight window freed the promise, and the completion then resolved whatever the allocator had reused the slot for — in the report, a RegExp header read as the promise's `next` inside the microtask pump. `js_promise_new_cross_thread` now takes the pin itself (one flag bit on a malloc-resident object it is already writing; the young-pin latch is not consulted) and the settlement paths release it (one byte test on the promise's own cache line). A token dropped without a settlement releases it too. The caller-side pins in `spawn` and `waitAsync` are gone; the stdlib bridge helper is now exactly the constructor. Every place a raw promise address re-enters the runtime from native code (the stdlib pump, the native-async token pump, the thread-result drain) classifies the address first and aborts naming the site and the slot's occupant, so a future rooting hole fails at the boundary instead of as heap corruption cycles later. `scripts/check_cross_thread_promise_provenance.py` (lint) finds arena promises handed to a native settlement sink or captured by a spawn, self-tested with three planted shapes and three clean ones. Claude-Session: https://claude.ai/code/session_01Bok4V8wzgNGmBeE4GPf7Up
…ise literals (#9565 follow-through)
proggeramlug
pushed a commit
that referenced
this pull request
Sep 2, 2026
…9565 The #9552 fix landed via merge train #9569 with the first draft of its gap test. That draft — `await fetch()` in a plain async function plus `gc()` — passed 6/6 on the UNFIXED runtime: the collection it forces does not free the in-flight promise, so the gate could not fail. This is the fixture validated on both arms: three consumer shapes (`.then`, async arrow, async class method) start a request from a frame that has returned, `Symbol()` churn trips the malloc-count sweep, `RegExp` headers reuse freed 80-byte slots. Unfixed it hangs 4/4 (two of the three promises freed by malloc sweeps, then a stale resolve on a reused slot); fixed it prints node's `ok,ok,ok 6` 5/5. Also lands the changelog fragment for #9565, which the train did not carry. Claude-Session: https://claude.ai/code/session_01Bok4V8wzgNGmBeE4GPf7Up
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lands the validated train: #9564 (bundled mysql2 fallback aligned with the ext extension — reflective/dynamic dispatch, query options, rowsAsArray, Error metadata; Drizzle pins one checked-out connection) and #9565 (cross-thread promises pinned in their constructor until settlement — closes the ~110-site class behind #9552's use-after-free; adds the provenance gate, self-tested and wired into lint).
Train fix: three gc test-fixture Promise literals gained the new
native_pinnedfield.Validation: release build green; RUST_TEST_THREADS=1 perry-runtime 3027/0; perry-stdlib (bundled-mysql2) green single-threaded; #9552 gap fixture byte-identical to node; mysql2 fixtures skip without a live DB (author ran them live end-to-end); new provenance gate green with working self-test; lint gates green.
Rebase-merge preserving authorship.