Skip to content

threading: defer post-fork weakref cleanup to a safe dispatch point - #1154

Merged
youknowone merged 2 commits into
mainfrom
agent/stdlib-foundations
Aug 11, 2026
Merged

threading: defer post-fork weakref cleanup to a safe dispatch point#1154
youknowone merged 2 commits into
mainfrom
agent/stdlib-foundations

Conversation

@youknowone

@youknowone youknowone commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • defer post-fork tracing-GC weakref cleanup to a root-complete bytecode safepoint
  • track eval nesting even when interpreter allocation routing is disabled
  • keep refused semantic requests outside the JIT breaker until callback unwind
  • preserve strongly referenced old MainThread objects while matching CPython dangling-thread behavior
  • promote test.test_threading to PASS

The earlier heap-type optimization was removed after parity review; subclass registration remains on PyPy add_subclass semantics.

Verification

  • cargo fmt --all -- --check
  • cargo check --features dynasm
  • cargo test --features dynasm
  • test.test_threading passes five consecutive dynasm JIT-on runs
  • test.test_thread and test.test_threading pass with dynasm JIT on and off
  • re-entrant sorted(key=...) post-fork fixture passes with JIT on and off
  • dynasm benchmark/selfcheck suite: 17/17 PASS
  • the three previously failing wasm snapshot targets pass: closure_per_call, getattr_hook_binding, recursion_memo_branch

Summary by CodeRabbit

  • Bug Fixes
    • Improved garbage-collection handling across interpreter and JIT execution.
    • Deferred major cleanup now runs safely after creating a child process.
    • Garbage-collection requests are preserved during nested interpreter activity and processed at an appropriate safe point.
    • Improved consistency of old-generation memory cleanup, helping reduce delayed resource reclamation.
    • Resolved a threading test issue, restoring expected test behavior.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 455bdd12-07e0-4b2f-8d0c-3d673f741dec

📥 Commits

Reviewing files that changed from the base of the PR and between f073f40 and 44f0783.

📒 Files selected for processing (5)
  • majit/majit-gc/src/collector.rs
  • pyre/cpython_tests/baseline.json
  • pyre/pyre-interpreter/src/eval.rs
  • pyre/pyre-jit/src/eval.rs
  • pyre/pyre-object/src/gc_interp.rs

Walkthrough

The PR adds explicit deferred old-generation collection requests, tracks nested evaluation depth, handles requests at interpreter and JIT safepoints, and schedules collection after child-side fork. The threading DynASM baseline changes from FAIL to PASS.

Changes

Deferred collection flow

Layer / File(s) Summary
GC request and safepoint handling
majit/majit-gc/src/collector.rs, pyre/pyre-object/src/gc_interp.rs
The collector exposes deferred major-collection requests. gc_interp tracks evaluation nesting, defers nested requests, and performs non-moving old-generation collection at the outermost eligible safepoint.
Dispatch and fork integration
pyre/pyre-interpreter/src/eval.rs, pyre/pyre-jit/src/eval.rs, pyre/pyre-interpreter/src/module/posix/interp_posix.rs, pyre/cpython_tests/baseline.json
Interpreter and JIT loops handle both GC breaker bits. Child-side fork schedules deferred collection. The test.test_threading DynASM baseline changes to PASS.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ForkChild
  participant request_oldgen_collection
  participant EvalLoop
  participant safepoint
  participant OldGenerationGC
  ForkChild->>request_oldgen_collection: queue deferred collection
  request_oldgen_collection->>EvalLoop: arm GC eval-breaker
  EvalLoop->>safepoint: process EB_GC or EB_GC_INTERP
  safepoint->>OldGenerationGC: collect old generation at eligible depth
Loading

Possibly related PRs

Suggested reviewers: lifthrasiir

Poem

I queued a collection, then hopped through the gate,
At the safepoint boundary, I would not be late.
Nested calls wait while the outer one runs,
Old-generation cleanup now follows the sun.
After fork, I twitch my bright nose:
“Collect when the next bytecode flows!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: deferring post-fork threading weakref cleanup to a safe dispatch point.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/stdlib-foundations

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 44f0783).
Updated: 2026-08-11T11:56:43.878Z

Files in the reviewed diff
majit/majit-gc/src/collector.rs
pyre/cpython_tests/baseline.json
pyre/pyre-interpreter/src/eval.rs
pyre/pyre-interpreter/src/module/posix/interp_posix.rs
pyre/pyre-jit/src/eval.rs
pyre/pyre-object/src/gc_interp.rs

1. Regressions to PyPy parity introduced by this patch

  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs:5940 ↔ pypy/module/posix/interp_posix.py:1580: pyre now queues a mandatory old-generation collection after child fork hooks; PyPy runs the child hooks and immediately returns (:1583). This changes child-observable GC timing: weakrefs may be cleared and GC finalizer/destructor work may be queued before the first post-fork Python bytecode. Main did not add this collection.

2. Other mismatches introduced by this patch

None.

3. Pre-existing mismatches (already present before this patch)

None.

4. Structural adaptations

  • pyre/pyre-object/src/gc_interp.rs:319 ↔ pypy/module/gc/interp_gc.py:19: the new deferred path uses a non-moving old-generation collection, whereas PyPy’s explicit collection uses full rgc.collect(). This is a Rust-stack-root safety adaptation; a moving nursery collection is unsafe without the translated shadow-stack rooting model.
  • pyre/pyre-object/src/gc_interp.rs:53 ↔ rpython/memory/gctransform/shadowstack.py:217: pyre tracks eval nesting in Rust TLS to defer collection to a root-complete bytecode boundary, while RPython’s fork handling relies on translated shadow-stack state. This is a free-threading/implementation-language adaptation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f073f4074e

ℹ️ 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".

Comment thread pyre/pyre-object/src/gc_interp.rs Outdated
if !requested && !enabled() {
return;
}
if !at_outermost_activation() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Track nesting while explicit GC requests are enabled

When PYRE_GC_INTERP is off (the native default), EvalActivationGuard::enter() does not increment EVAL_NESTING, so this new gate always reports root completeness. If os.fork() is called from a re-entrant Python callback such as a sorted() key function, the child services the explicit request at the callback's next opcode even though the outer native opcode handler still holds unregistered PyObjectRefs on its Rust stack; the non-moving major can free those old-generation objects and leave dangling references. Explicit requests need an activation counter that remains accurate even when interpreter allocation routing is disabled.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5af881f: EvalActivationGuard now tracks per-thread nesting regardless of PYRE_GC_INTERP, while armed continues to represent only the allocation-routing feature gate.

Comment thread pyre/pyre-object/src/gc_interp.rs Outdated
Comment on lines +282 to +284
if requested {
majit_gc::collector::request_deferred_major_collection();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not re-arm refused requests in the breaker word

With PYRE_GC_INTERP=1, a child fork performed at callback depth 3 or greater reaches this branch on every subsequent opcode: the safepoint consumes EB_GC and immediately sets it again. Until the callback unwinds, interpreter dispatch repeatedly enters this residual and compiled backedges repeatedly deopt because EB_GC is in JIT_BREAKER_MASK; a callback that continues a long loop after fork() can therefore suffer unbounded slowdown and bridge churn. Preserve the deferred request outside the active breaker bit and re-arm it only after returning to an eligible activation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5af881f: refused semantic requests remain in a process-global pending bit outside EB_GC; note_eval_activation_exit re-arms EB_GC once when depth becomes eligible. The re-entrant sorted(key=...) fork fixture now completes immediately with JIT on and off.

Comment thread pyre/pyre-object/src/typeobject.rs Outdated
Comment on lines +1525 to +1528
if is_fresh {
w_type_add_fresh_subclass(w_base, w_self);
} else {
w_type_add_subclass(w_base, w_self);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep duplicate detection for fresh type registration

When a custom metaclass mro() re-entrantly assigns cls.__bases__ during construction, type_set_bases calls w_type_ready_existing and registers the nascent class on its new base before the outer construction resumes. This branch then assumes the class is fresh and blindly adds it again, so the base's __subclasses__() contains the same class twice and invalidation traverses it twice. Initial ready() must retain upstream add_subclass's identity scan rather than introducing this fresh-only shortcut, consistent with the repository's strict structural-parity requirement.

AGENTS.md reference: AGENTS.md:L231-L233

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by removing the heap-type optimization commit from the PR entirely. The diff now retains PyPy w_type_add_subclass identity scan and dead-slot replacement ordering unchanged.

@youknowone
youknowone force-pushed the agent/stdlib-foundations branch from f073f40 to 5af881f Compare August 11, 2026 09:12
@youknowone youknowone changed the title stdlib: finish threading fork cleanup and heap-type registration threading: defer post-fork weakref cleanup to a safe dispatch point Aug 11, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5af881f2c3

ℹ️ 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".

// here would run while this native builtin still
// owns unregistered Rust-stack temporaries, and a
// moving full collection would be unsafe.
pyre_object::gc_interp::request_oldgen_collection();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not force an old-generation collection after fork

When automatic GC is disabled and the child inherits an unreachable cycle, this unconditional request still sweeps the entire old generation at the next opcode, clearing weakrefs and potentially scheduling finalizers that neither CPython nor PyPy runs merely because fork() returned. For example, after gc.disable(); a.self = a; r = weakref.ref(a); del a, CPython preserves r() in the child across os.fork(), while this path collects a; fixing _MainThread's acyclic refcount-timing difference with a process-wide tracing pass therefore changes unrelated observable lifetime semantics and conflicts with the required PyPy structural parity.

AGENTS.md reference: AGENTS.md:L231-L233

Useful? React with 👍 / 👎.

@youknowone
youknowone merged commit d6b6547 into main Aug 11, 2026
17 checks passed
@youknowone
youknowone deleted the agent/stdlib-foundations branch August 11, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant