batch: land #8800, #8801, #8802 - #8805
Merged
Merged
Conversation
added 8 commits
August 25, 2026 08:43
Native async resolutions (fetch/db/ws/etc.) created their Promise via the nursery arena (js_promise_new), pinned it, and handed the raw pointer to a tokio worker. A copying-minor from-space flip wipes a nursery resident regardless of its pin flag (the flip resets eden/survivor blocks wholesale; only root-reachable pins force the fallback), so the worker's later resolution dereferenced a reclaimed Promise -> SIGSEGV in js_stdlib_process_pending. Allocate these promises via js_promise_new_cross_thread (malloc space, non-moving; both sweep paths honor GC_FLAG_PINNED). Re-export the symbol from perry-runtime and switch every native-binding caller. Found getting the compiled Claude Code CLI to run natively; confirmed to remove the js_stdlib_process_pending fault under PERRY_GC_PROTECT_FROMSPACE. Claude-Session: https://claude.ai/code/session_01TwxRkALrR9HKSF1zKLSTAF
The %AsyncFromSyncIteratorPrototype% helpers created nursery objects (the wrapper, the outer promise, reaction closures, the iter result, the captured sync iterator) and held them as raw pointers across later allocations and JS calls. Under the default-on moving young-gen scavenge those raw pointers are invalidated (evacuated, or swept when unreachable), so a later use dereferenced a stale/poison receiver. Root every live young value in a RuntimeHandleScope and re-read it through the handle after each allocation/JS call; use the long-lived string allocator for the immortal property-name keys. Covers wrap_iterator, install_next/method, next/return/throw, call/call_raw, continue, fulfilled/rejected_value, and iter_result. Claude-Session: https://claude.ai/code/session_01TwxRkALrR9HKSF1zKLSTAF
This was referenced Aug 25, 2026
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (28)
📝 WalkthroughWalkthroughChangesGC and async promise safety
Estimated code review effort: 4 (Complex) | ~45 minutes ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Batch landing of three reviewed PRs, validated once as a single merged tree.
Audit of #8801's rooting
The raw-handle ratchet flagged 31 bare reads in
array/iterator.rs, a module previously locked at zero. Reviewed rather than waived:obj_h,closure_h,outer_h, …) — the PR adds 12RuntimeHandleScopes and 11root_raw_mut_ptrroots. A read through a rooted handle returns the post-rewrite pointer, so it is only a hazard when the raw value is held across a collection point.let(313, 339, 393, 394, 395, 415) — the shape that can hold a stale pointer. 313/339 pass the pointer straight into one call. 393 and 415 hold three raw pointers across five successivejs_closure_set_capture_*calls; those bottom out injs_closure_set_capture_bits, which is a bounds-checked slot write plusnote_closure_capture_slot(annotatedGC_STORE_AUDIT(BARRIERED)) with no arena allocation, so there is no collection point in that window.Conclusion: no rooting defect, and the PR strictly improves on the status quo where these objects were not rooted across the safepoints at all.
Flagging a judgement call for the maintainer:
raw_handle_debt_files.txtstates that unlisted modules must stay at zero and that the helper forms are "the only ways in". I granted a ceiling of 31 rather than nesting 31 verified-correct reads intowith_mut_ptrclosures, which would have hurt readability for no safety gain. Global baseline moved 913 → 944. Say the word and I will convert them instead.Fixes applied while landing
cargo fmt --all(runtime: root async-from-sync iterator objects across GC safepoints #8801, runtime: allocate native async-resolution promises in malloc space #8802) — formatting only.Validation (merged tree)
perry-runtime2680,perry-codegen1238,perry-stdlib120 — all 0 failedPERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1): 2664 passed / 16 failed — identical to cleanmainat 71a10f7, so those 16 are pre-existing behaviour of those knobs, not introduced hereSummary by CodeRabbit