gc: fix collection fallbacks, shared actions, and heap dumps - #1160
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe PR corrects fallback GC transitions, adds sandbox-aware heap-dump writing, and moves finalizer and GIL actions to process-wide storage. It also adds managed string construction, collectability probes, worker-thread GC-hook coverage, and JIT statistics fixtures. ChangesShared interpreter actions
Heap-dump write integration
GC state correction
Managed string construction and fixtures
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant WorkerExecutionContext
participant SpaceActionFlag
participant GilInitialize
participant ThreadRootWalker
WorkerExecutionContext->>SpaceActionFlag: access shared action flag
WorkerExecutionContext->>GilInitialize: register GIL action once
GilInitialize->>ThreadRootWalker: expose retained action root
ThreadRootWalker-->>WorkerExecutionContext: trace action reference
Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
There was a problem hiding this comment.
💡 Codex Review
https://github.com/youknowone/pyre/blob/32dc502ba2ebab122734b9ac23fb4239a40a6c0f/pyre-interpreter/src/executioncontext.rs#L1967-L1970
Keep signal dispatch on the signal-enabled thread
Sharing this ticker means any worker can now enter the shared periodic-action dispatcher when a signal makes it negative. However, CheckSignalAction::poll_for_signals_unlocked still unconditionally calls report_signal(ec, n) under its obsolete “pyre is single-threaded” assumption (module/signal/interp_signal.rs:316-339). Thus, if a worker reaches an opcode before the main interpreter thread after SIGINT or another handled signal, the Python handler—and commonly KeyboardInterrupt—runs on that worker. PyPy's interp_signal.py:128-139 instead checks threadlocals.signals_enabled() and defers via fire_in_another_thread until the main thread resumes; that thread-sensitive path must be ported as part of making the action flag shared.
AGENTS.md reference: AGENTS.md:L231-L232
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
🤖 Codex parity reviewStatic analysis of this diff vs the local RPython/PyPy sources (commit db5be09). Files in the reviewed diff1. Regressions to PyPy parity introduced by this patch
2. Other mismatches introduced by this patchNone. 3. Pre-existing mismatches (already present before this patch)
4. Structural adaptations
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pyre/bench/synth/gc_hook_worker_thread.py`:
- Line 16: Rename the unused parameter in the on_collect callback from stats to
_stats, preserving the callback’s signature and behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f7083db2-f106-4b00-a5be-a48a78684ca9
📒 Files selected for processing (14)
majit/majit-backend-cranelift/src/compiler.rsmajit/majit-backend-wasm/src/lib.rsmajit/majit-gc/src/collector.rsmajit/majit-gc/src/lib.rspyre/bench/synth/gc_hook_worker_thread.cranelift.jitstatspyre/bench/synth/gc_hook_worker_thread.dynasm.jitstatspyre/bench/synth/gc_hook_worker_thread.pypyre/pyre-interpreter/src/eval.rspyre/pyre-interpreter/src/executioncontext.rspyre/pyre-interpreter/src/module/gc/hook.rspyre/pyre-interpreter/src/module/gc/mod.rspyre/pyre-interpreter/src/module/signal/interp_signal.rspyre/pyre-interpreter/src/module/thread/gil.rspyre/pyre-interpreter/src/module/thread/mod.rs
| done.acquire() | ||
|
|
||
|
|
||
| def on_collect(stats): |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Rename the unused callback parameter.
Ruff reports stats as unused. Rename it to _stats and preserve the callback signature.
Proposed fix
-def on_collect(stats):
+def on_collect(_stats):📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def on_collect(stats): | |
| def on_collect(_stats): |
🧰 Tools
🪛 Ruff (0.16.1)
[warning] 16-16: Unused function argument: stats
(ARG001)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/bench/synth/gc_hook_worker_thread.py` at line 16, Rename the unused
parameter in the on_collect callback from stats to _stats, preserving the
callback’s signature and behavior.
Source: Linters/SAST tools
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ebeb5beefd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| #[cfg(feature = "sandbox")] | ||
| fn heap_dump_write_via_host(fd: i32, bytes: &[u8]) -> Result<isize, i32> { | ||
| crate::host_seam::ops::write(fd, bytes) |
There was a problem hiding this comment.
Avoid releasing the GIL while the heap dump owns STW
In a sandbox build with more than one registered mutator, this call deadlocks: do_dump_rpy_heap holds a StwGuard (collector.rs:3538-3543), while the sandbox ops::write wrapper enters before_external_block (host_seam.rs:457-460). When that blocking guard is dropped, it waits for stw_requested to become false (gc_sync.rs:334-338), but this thread cannot clear the request until the write returns and its own StwGuard is dropped. Consequently, gc.dump_rpy_heap hangs whenever another runtime thread remains registered; this path needs a host write variant that does not leave the GIL/RUNNING census while the caller owns STW.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
https://github.com/youknowone/pyre/blob/49739b06e757d831da145796d44e04023e77c792/pyre-interpreter/src/module/signal/interp_signal.rs#L432
Keep signal polling on the main thread
When a signal arrives while a worker owns the GIL, registering CheckSignalAction on the shared flag lets that worker run poll_for_signals_unlocked first. Unlike upstream's pypy/module/signal/interp_signal.py:125-140, this implementation has no signals_enabled() gate; moreover, HANDLERS is thread-local (interp_signal.rs:80-109), so the worker consumes the process-wide pending signal, finds no handler in its empty table, and returns before the main thread can deliver it. This can silently swallow SIGINT or user-installed handlers in threaded programs; retain the shared action but defer signal consumption to the main thread as upstream does.
AGENTS.md reference: AGENTS.md:L148-L155
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1cf178703b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let action: &'static mut CheckSignalAction = Box::leak(CheckSignalAction::new(ec.space)); | ||
| let async_ptr: *mut dyn AsyncActionOps = &mut *action; | ||
| action.register_periodic_action(&mut ec.actionflag, false); | ||
| action.register_periodic_action(ec.actionflag.shared_mut(), false); |
There was a problem hiding this comment.
Defer signal polling from worker threads
With multiple Python threads, registering CheckSignalAction on the newly shared flag lets whichever worker next dispatches the ticker run poll_for_signals; the existing implementation at interp_signal.rs:316-340 unconditionally consumes the pending signal and calls its Python handler with that worker's execution context. Before this change, only the boot context's flag contained this action. Upstream pypy/module/signal/interp_signal.py:128-140 instead checks signals_enabled() and defers delivery until switching to the main thread, so this shared registration needs the same gate/handoff or a pending SIGINT can raise KeyboardInterrupt in a worker while the main thread continues.
AGENTS.md reference: AGENTS.md:L231-L233
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pyre/bench/synth/gc_json_string_collectable.py`:
- Around line 22-27: Keep the assertion in the JSON error-note test tied
exclusively to the note produced by json.dumps/add_json_note: when the runtime
supports JSON context notes, fail if notes is empty instead of calling
exc.add_note. If older PyPy compatibility is required, isolate it behind an
explicit runtime capability check and ensure the GC ownership assertion still
validates the JSON-created note rather than the synthetic fallback.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 07577829-8aca-42d5-819b-60aef336c6c5
📒 Files selected for processing (2)
pyre/bench/synth/gc_json_string_collectable.pypyre/pyre-interpreter/src/module/_json/mod.rs
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5788712637
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| impl PeriodicAsyncActionOps for GilReleaseAction {} | ||
|
|
||
| static GIL_RELEASE_ACTION: OnceLock<usize> = OnceLock::new(); |
There was a problem hiding this comment.
Prevent mutable aliasing of the shared GIL action
When two Python threads are runnable and the periodic ticker fires, action_dispatcher invokes this singleton through &mut dyn AsyncActionOps; perform then calls yield_thread(), leaving that mutable borrow live while another thread acquires the GIL and can dispatch the same singleton through a second &mut. Shared GC actions have the same problem when their Python callbacks release the GIL. This is undefined behavior and can corrupt action state, so keep the action process-owned but redesign dispatch so no exclusive Rust borrow of a shared action spans a GIL handoff or app-level call.
AGENTS.md reference: AGENTS.md:L148-L155
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f1d627839c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let action: &'static mut CheckSignalAction = Box::leak(CheckSignalAction::new(ec.space)); | ||
| let async_ptr: *mut dyn AsyncActionOps = &mut *action; | ||
| action.register_periodic_action(&mut ec.actionflag, false); | ||
| action.register_periodic_action(ec.actionflag.shared_mut(), false); |
There was a problem hiding this comment.
Reuse the process-owned signal action
When a process creates another top-level execution context, such as the -i path that runs a script and then enters run_repl, the new EC has check_signal_action == None, so this allocates and registers another leaked CheckSignalAction. Because the action flag is now process-owned, the previous action remains in its periodic list; every ticker subsequently dispatches both, and embeddings that invoke the runner repeatedly grow this list without bound. Store/reuse the signal action with the shared object-space state rather than guarding installation on each EC.
AGENTS.md reference: AGENTS.md:L148-L155
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c544ce3ebd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| fn decrement_ticker(&mut self, by: isize) -> isize { | ||
| self.inner_mut().decrement_ticker(by) |
There was a problem hiding this comment.
Use synchronized access for the shared signal ticker
With multiple Python threads, this newly shared delegation makes whichever GIL-owning worker is executing bytecode read and modify the same plain isize ticker that the OS signal handler writes through signalstate::rearm_ticker using ptr::write_volatile. The GIL does not serialize an asynchronous handler running on another OS thread, and volatile access is not atomic in Rust, so a signal arriving while a worker calls decrement_ticker creates a data race (undefined behavior, with possible missed or corrupted ticker updates). The process-owned ticker needs signal-safe synchronized storage/access rather than ordinary isize reads and writes.
Useful? React with 👍 / 👎.
53efead to
6719b46
Compare
…_step trampolines Both are installed into ACTIVE_COLLECT_STEP, so they stood in front of the already-corrected majit-gc fallback and the trait default. Assisted-by: Claude
gc_query_reentrant reaches singleton_ref, which panics when store_singleton has not run; would_collect asks this from the interpreter allocation path. Assisted-by: Claude
Assisted-by: Claude
HeapDumpWriter reached the descriptor with a raw libc::write. Under --features sandbox that descriptor is the guest's, so gc._dump_rpy_heap(1) put dump bytes on the marshalling pipe and a virtualized descriptor named an unrelated host fd. The write now goes through a process-global hook that the sandbox build fills in with host_seam::ops::write; with no hook installed the native path is unchanged. HEAP_DUMP_EIO is exported so both sides name one constant, and its comment now states pyre's own reason for a fixed code rather than attributing it to inspector.py, which raises the real errno (inspector.py:212-223). Assisted-by: Claude
PyPy owns the action flag, user-finalizer action, and GIL release action on the object space. Pyre instead gave each execution context a separate flag, so a worker collection fired a GC hook bit that only the boot context dispatched.\n\nKeep one process-owned flag and action instance, root their managed fields once, and add a worker-thread oracle fixture that verifies the hook runs on the collecting thread.
6719b46 to
1195dfd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1195dfdcbe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for &w_arg in group_args { | ||
| let span = do_span(m, Some(w_arg))?; | ||
| results.push(unsafe { slice_w(m, span, w_none()) }); | ||
| let span = do_span(m.get() as *const W_SRE_Match, Some(w_arg))?; |
There was a problem hiding this comment.
Pin all group selectors before allocating slices
When Match.group() receives multiple selectors, the first slice_w now creates a managed string and can trigger a moving collection, but the remaining selectors are retained only as raw pointers in group_args. The builtin gateway's native argument copy is not forwarded during that collection, so a call such as m.group(dynamic_name1, dynamic_name2) can pass a stale second name to do_span, producing incorrect lookup or memory corruption when the nursery fills. Pin every selector before the loop and reload it from the shadow stack, as is already done for m and the results.
AGENTS.md reference: AGENTS.md:L231-L233
Useful? React with 👍 / 👎.
Follow-up fixes discovered after #1122 merged.
Summary
MARKING → SCANNINGtransition when the cranelift or wasmcollect_steptrampoline has no active collectorgc_isenabled()return the enabled-by-default result before the process-global collector is initializedspace.actionflag,space.user_del_action, and the GIL release action process-owned instead of duplicating them per execution contexthost_seam::ops::writewhile preserving the native raw-write fallback_jsonruntime strings—encode_basestring*results, decodedscanstringvalues, JSON error notes, float-key coercions, and one-shot encoder chunks—in the managed heap instead of leaking them as host-allocated immortal stringstypes.GenericAlias.__repr__results in the managed heap, matching PyPy's f-string resultContextVarandTokenrepr results in the managed heap instead of leaking each rendered stringarray.array.tounicode()results in the managed heap instead of leaking each converted Unicode bufferarray.array.__repr__results in the managed heap for both numeric and Unicode arrayscollections.deque.__repr__results in the managed heap, including maxlen-formatted resultsre.Pattern.__repr__results in the managed heap instead of leaking each formatted pattern stringre.Match.__repr__results in the managed heap instead of leaking each formatted match stringstr()result identity and managed lifetime by forwarding through the object-space string operationunicodedata.normalize()results in the managed heap and preserve PyPy's O(1) ASCII identity fast pathtypes.SimpleNamespace.__repr__results and recursive guard strings in the managed heapre.sub,re.subn, andMatch.expandUnicode results in the managed heap_sregroup, findall, split, and subclass-normalization slices in the managed heap with relocation-safe container assemblytime.strftime()results in the managed heap on both Unix and Windowstime.asctime()and nativetime.ctime()results in the managed heap through the shared upstream-style formatteros.DirEntry.__repr__results in the managed heap, matching PyPy'sspace.newtextresultmemoryview.__repr__results in the managed heap across native and Wasm backendsmmap.mmap.__repr__results in the managed heap on native backendswrap_raw_nodesdocumentation summaryRoot cause
PyPy stores its action flag and async GC/finalizer actions on the object space. Pyre stored the flag on each
ExecutionContext, while hook actions retained the boot context flag. A workergc.collect()therefore fired a bit that the worker dispatch loop never read; the callback waited until the boot thread reached an opcode and ran on the wrong thread. The shared action state now follows the upstream object-space ownership shape.The generic
w_str_from_wtf8constructor is an off-GC bootstrap/structural-string path._jsonused it for fresh runtime results fromencode_basestring*,scanstring, dynamically generated error notes, float-key coercions, and one-shot encoder chunks, so those strings and their WTF-8 buffers were immortal. They now usew_str_from_wtf8_managed; values that cross a subsequent tuple/list allocation are pinned on the shadow stack first. The PyPy oracle and the fixture verify that each observable result identity appears ingc.get_objects(). The explicittypes.GenericAlias.__repr__descriptor had the same defect even though ordinaryrepr(alias)already used a managed display path; its direct result now follows PyPy's ordinary f-string allocation.ContextVar.__repr__andToken.__repr__likewise returned freshly rendered values through the bootstrap constructor; both now use the managed path verified by the PyPy identity oracle. Structseq reprs had the same runtime-allocation defect;sys.version_infonow verifies the shared repr path on native and Wasm without relying on an OS module.array.array.tounicode()also returned a fresh conversion through the bootstrap constructor, including as an intermediate in Unicode-array repr; it now follows PyPy'sspace.newutf8managed allocation. The explicitarray.array.__repr__descriptor independently returned its final formatted string through the bootstrap constructor; its numeric and Unicode results now match PyPy's managedspace.newtextresult. The explicitcollections.deque.__repr__descriptor had the same final-result leak even though ordinaryrepr(deque)already passed through the managed display path; it now matches PyPy's%-formatted managed result. The explicitre.Pattern.__repr__descriptor likewise used the bootstrap constructor while PyPy returnsspace.newtext; its direct descriptor result is now managed. The explicitre.Match.__repr__descriptor had the identical bootstrap-constructor defect and now also follows PyPy's managedspace.newtextresult. Weak-proxy__str__diverged more deeply: PyPy forwards tospace.str(w_obj)and returns the referent's managed result object unchanged, while Pyre flattened it to WTF-8 and rebuilt an immortal copy. The proxy now forwards through the object-space-equivalentbuiltin_str, preserving both identity and GC lifetime._pickle's shared UTF-8 loader similarly rebuilt SHORT_BINUNICODE, BINUNICODE, and BINUNICODE8 values through the bootstrap constructor, while PyPy usesspace.newtextfor all three; unpickled Unicode values now use the managed constructor.unicodedata.normalize()also rebuilt every result through the bootstrap constructor. Non-ASCII normalization now returns managed base strings, while exact ASCII strings follow PyPy'sspace.newutf8buffer-identity behavior through an O(1) return; string subclasses still become managed base strings.types.SimpleNamespace.__repr__had the same final-result defect in both its ordinary formatted result and recursivenamespace(...)guard; both paths now use managed constructors, matching PyPy's app-level formatting behavior._sre's shared substitution-output builder also returned Unicodesub,subn, andMatch.expandresults through the bootstrap constructor, while PyPy usesspace.newutf8; the shared result now uses the managed constructor, andsubnreloads it from a shadow-stack slot across tuple allocation._sre::slice_subjecthad the same bootstrap-allocation defect acrossMatch.group/__getitem__/groups/groupdict,findall,split, and the no-match subclass-normalization path. These slices now use PyPy'sspace.newutf8-equivalent managed constructor. Tuple/dict assembly reloads translated live GCREFs from shadow-stack slots, whilefindallandsplitnow accumulate directly into a rooted managed list like PyPy instead of retaining every result in an off-heap RustVec, avoiding an O(number of matches) shadow stack.time.strftime()had the same runtime-string defect in both native implementations: PyPy returnsspace.newutf8(decoded, size), but Pyre's Unix and Windows valid-result branches used the bootstrap constructor. Both now return managed strings; the locale surrogateescape fallback was already managed.time.asctime()andtime.ctime()likewise returned freshly formatted values throughw_str_new. PyPy routes both through_asctimeand ordinary%formatting. Pyre now allocates the shared formatted result as managed and removes the separate Windows_ctime64branch so nativectime()follows the samelocaltime → _asctimeshape on Unix and Windows.The explicit
os.DirEntry.__repr__descriptor also assembled a fresh<DirEntry ...>value through the bootstrap WTF-8 constructor, while PyPy returnsspace.newtext; its direct result is now managed, matching the already-managed ordinaryrepr(entry)display path.The explicit
memoryview.__repr__descriptor formatted both live<memory at ...>and released<released memory at ...>values throughw_str_new, while PyPy's sharedW_Root.getreprreturnsspace.newtext. Both labels now use the managed constructor, matching the already-managed ordinaryrepr(view)path on native and Wasm.The shared
W_WeakrefBase.descr__repr__path forReferenceType,ProxyType, andCallableProxyTypelikewise returned live and dead labels throughw_str_new, while PyPy delegates all of them toW_Root.getreprandspace.newtext. The shared result now uses the managed constructor, covering both explicit descriptor calls and ordinaryrepr()for all three weak-reference kinds in both states.W_MMap.descr_reprhad the same defect in both branches: live mappings formatted their access, length, position, and offset throughw_str_new, while closed mappings returned a raw structural string. PyPy returnsspace.newtextafter%formatting in both cases, so both native paths now allocate managed results; the oracle fixture verifies explicit and ordinary repr identities before and afterclose().Validation
pypy3 pyre/bench/synth/gc_hook_worker_thread.pyPYPYLOG=jit-summary:- pypy3 pyre/bench/synth/gc_json_string_collectable.pycargo fmt --all --checkcargo check --features dynasmcargo test --features dynasmcargo check -p pyrex --bin pyre --features sandboxpython3 pyre/check.py --backend dynasm,cranelift --no-synthetic --no-cpython-suite— dynasm 17/17, cranelift 17/17python3 pyre/check.py --backend dynasm,cranelift,wasm --synthetic-only --synthetic-pattern gc_*.py --no-cpython-suite— dynasm 28/28, cranelift 28/28, wasm 22/22Summary by CodeRabbit
Bug Fixes
Tests