Skip to content

Track Unpickler payload references during cyclic GC - #9288

Merged
youknowone merged 1 commit into
RustPython:mainfrom
youknowdot:split/unpickler-payload-gc-315
Oct 22, 2026
Merged

youknowone merged 1 commit into
RustPython:mainfrom
youknowdot:split/unpickler-payload-gc-315

Conversation

@youknowdot

Copy link
Copy Markdown
Contributor

Track Unpickler payload references during cyclic GC

Summary

Split the remaining Unpickler GC/lifetime closure from the Python 3.15 upgrade work. This changes crates/stdlib/src/pickle.rs (+68/-17) on main base bcfad3554 and appends two nonduplicative payload-cycle regressions to the existing extra_tests/snippets/stdlib_pickle.py (+41). It preserves the existing allocation/copy handling and private-name lookup.

  • Traverse the owned read, readline, and peek slots, the buffer iterator, and every occupied memo slot, including sparse keys and repeated identities held by distinct strong slots.
  • Traverse the memo proxy's reference to its Unpickler.
  • Clear Unpickler outgoing references into the collector's deferred-drop vector and invalidate initialization state.
  • On successful reinitialization, replace the old state/configuration and retire their references with the old memo after releasing the state lock.

No Lib changes, new snippet files, Rust Python-behavior tests, collector changes, or CPython-only decorator changes are included.

Bounded validation

Source-bound candidate built with the main Ruff pin 9290a884; compiler/codegen/VM/stdlib/pylib/root were explicitly invalidated and actually recompiled. All tracked source hashes and symlink targets matched the reviewed source after building. Prior sealed binaries were preserved.

An external review harness ran each mode in a separate fresh process, with a fixed 20-second deadline, 512 MiB sampled RSS, 6 GiB address-space limit, core dumps disabled, and resource reserve guards:

  • Direct reference identities/multiplicity: candidate read/readline/peek/buffers/memo/proxy counts are 1/1/1/1/2/1. Main baseline reports 0/0/0/0/0/0.
  • One tiny memo, stream, or buffers cycle through a memo proxy, tested separately with callback-free weakrefs and exactly three collections: candidate passes all three; main baseline fails their release assertions.
  • Successful ordinary reinitialization replaces stream/memo/buffers, releases old holders after three collections, and loads a locally serialized three-integer list: candidate and baseline both pass. This observation does not prove lock-drop ordering; that conclusion comes from source review.

Two existing canonical CPython 3.15.0 compatibility methods, test_priming_unpickler_memo and test_reusing_unpickler_objects, pass on baseline, candidate and CPython, two tests and zero skips each. Their unchanged methods and setup were AST-selected into an external harness with _pickle bindings and only io, _pickle, and unittest globals. No broad pickle test-module import or whole-suite execution occurred.

There were no crashes, hangs, guard stops, automatic retries, or weakened assertions. Inputs were locally serialized ordinary values; no malformed streams, stress/OOM, native-memory probes, reentrant finalizers or concurrent mutation were tested.

The existing pickle snippet now retains the stream and buffers cycle regressions. Canonical persistent-load reference-cycle and buffer-content tests do not cover these payload cycles. Only the new definitions and loop body were AST-selected and validated, one case per fresh guarded process: baseline fails both release assertions, while CPython and candidate pass both. The complete pre-existing snippet was not executed because its unrelated oversized-length cases are outside this local gate. Its required full pytest gate remains unrun.

CPython 3.15.0 oracle qualification

The pinned oracle does not pass every harness assertion. Its direct edge counts are 1/1/1/1/0/1, failing the two-slot memo assertion. A separately bounded memo.copy() observation returns an empty dict immediately after assignment on CPython, whereas main RustPython preserves keys 0 and 17 with both holder identities.

Source inspection of pinned _pickle.c explains this: the dict memo setter writes values into the old memo, then cleans that memo and installs the newly allocated empty array. Consequently the oracle's memo-cycle pass is vacuous for the intended cycle. We preserve this discrepancy rather than weaken the Rust ownership invariant or claim full oracle parity. Oracle stream/buffers/reinit modes pass independently.

Safety and scope qualifications

  • Collector clear is invoked after world restart, under the existing exclusive-unreachable-object/deferred-drop contract. This patch inherits that contract and these tests do not prove it. It is not justified by stop-the-world alone.
  • The derived memo proxy's clear remains a no-op. Clearing Unpickler outgoing edges breaks the reviewed cycles; this is not complete CPython proxy-clear parity.
  • Deferred retirement applies to the successful __init__ path only. Failure-path locals can still drop under the state lock; no reentrant-finalizer claim is made.
  • No hard API dependency on Prepare interpreter-local runtime state and thread lifecycle for GC isolation #8944 was found, but coordination on GC architecture and exclusivity assumptions remains necessary.

Additional gates

Passed under the same build resource guard:

  • cargo check --locked --offline -p rustpython-stdlib --features threading (128s).
  • cargo check --locked --offline -p rustpython-stdlib (non-threaded, default features; 54s).
  • cargo clippy --locked --offline -p rustpython-stdlib --all-targets --features threading -- -D warnings (218s).
  • prek run --files crates/stdlib/src/pickle.rs (4s; no source changes).

All 3,631 source manifest entries matched after the native gates. The subsequently approved snippet-only addition is the sole manifest difference from the source-bound binary. Native source and sealed binary remain unchanged. Normal hooks were then rerun on both changed files and passed in 2s without rewrites; the two added cases were validated against that same sealed binary.

The required broader workspace/CAPI runtime suites and release pickle-module command have not been run and are not waived. This bounded validation is not a collector/threading soundness proof.

AI assistance: OpenAI assistant assisted with source review, implementation preparation and bounded validation.

Traverse input callbacks, buffer iterators, memo slots and memo proxy owners.
Clear outgoing payload edges through deferred drops and retire successful
reinitialization state outside the state lock. Retain stream and buffers
payload-cycle regressions in the existing pickle snippet.

Assisted-by: OpenAI Codex:model-version-unavailable
@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Repository: RustPython/RustPython/.coderabbit.yml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d95d6cd8-f41d-425d-b7bb-5065b3061beb
📥 Commits

Reviewing files that changed from the base of the PR and between bcfad35 and c9708f3.

📒 Files selected for processing: 2
  • crates/stdlib/src/pickle.rs
  • extra_tests/snippets/stdlib_pickle.py
+-----------------------------------+
| I like big bugs and I cannot lie. |
+-----------------------------------+
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@youknowone
youknowone marked this pull request as ready for review October 22, 2026 01:17
@youknowone
youknowone merged commit f482e0c into RustPython:main Oct 22, 2026
21 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T01:21:39.532446Z c9708f3 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: c9708f3e45

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



# Unpickler payloads can retain a cycle through their memo proxy.
class UnpicklerCycleStream:

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 Remove newly added test logic

This newly added regression block changes test logic in an existing test file, but the repository's test-modification policy says test logic, data, and assertions must never be modified and limits acceptable test-file changes to adding or removing expectedFailure decorators; revert this block rather than landing an unsupported test-file edit.

AGENTS.md reference: AGENTS.md:L278-L282

Useful? React with 👍 / 👎.

return weakref.ref(holder)


for kind in ("stream", "buffers"):

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 Run the required validation suites

For this interpreter change and the added extra_tests/snippets code, the commit's validation explicitly says the workspace/C-API suites and full pytest gate were not run; repository policy requires the workspace test command, C-API cargo test, and pytest -v in extra_tests (plus relevant interpreter-behavior tests) before completion, so run those gates before landing.

AGENTS.md reference: AGENTS.md:L35-L35

Useful? React with 👍 / 👎.

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.

2 participants