Skip to content

weakref: reuse proxy objects#8307

Merged
youknowone merged 4 commits into
RustPython:mainfrom
rlaisqls:feat/weakref-proxy-reuse
Jul 18, 2026
Merged

weakref: reuse proxy objects#8307
youknowone merged 4 commits into
RustPython:mainfrom
rlaisqls:feat/weakref-proxy-reuse

Conversation

@rlaisqls

@rlaisqls rlaisqls commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Make callback-less weakref.proxy objects reusable, like CPython. When weakref.proxy(o) is called twice on the same object, CPython returns the same proxy object, but RustPython has always created a new proxy each time.

With this change, the @unittest.expectedFailure markers are removed from two tests in test_weakref.py: test_proxy_reuse and test_shared_proxy_without_callback.

Background

PyWeakProxy was a separate wrapper struct ({ weak: PyRef<PyWeak> }) pointing at an inner PyWeak node. With this structure, even if the inner PyWeak node was reused, the outer wrapper was freshly allocated on every call, so is identity between two weakref.proxy(o) calls could never hold in the first place. This is exactly the problem the // TODO: PyWeakProxy should use the same payload as PyWeak comment was pointing at.

Moreover, WeakRefList which holds the reuse candidates is a linked list whose nodes are PyWeak struct pointers, so PyWeakProxy, being a separate struct, could not be placed in that list and was never even eligible for the reuse cache.

However, since PyWeakProxy is an empty shell with no fields of its own besides the single PyRef<PyWeak>, I judged that this gap could be closed by merging its payload with PyWeak, and implemented it that way.

Changes

  • Change PyWeakProxy to a #[repr(transparent)] view sharing PyWeak's payload. It uses a manual PyPayload implementation that shares PAYLOAD_TYPE_ID with PyWeak and distinguishes downcasts via a class check.
    • Since the proxy object is now itself a node in the weakref list, the reuse logic in WeakRefList::add() applies to proxies as-is.
  • Apply CPython's insert_weakref / get_basic_refs slot discipline:
    • The generic ref goes at the head of the list (idx 0),
    • And the generic proxy right after it (idx 1),
    • And before reuse we re-verify that the callback state and the class match exactly
      (subclasses are excluded from reuse)
  • Move the constructor to a slot_new override. Since reuse must return an existing object rather than a new payload, this is handled in slot_new instead of py_new.
  • Remove the hidden __weakproxy marker subclass, and since proxies now downcast to PyWeak, drop the proxy branch in the capi PyWeakref_GetRef as well.

Summary by CodeRabbit

  • Bug Fixes
    • Tightened weak-reference upgrade behavior: weak upgrades now accept only direct weak references; unsupported weak-proxy inputs raise a clear TypeError.
    • Improved weak and weak-proxy reuse during creation, including safer handling when multiple upgrades occur concurrently.
    • Fixed weak-proxy construction so the referent and optional callback are applied correctly.
    • Reduced the likelihood of duplicate or inconsistent weak-list entries under concurrent operations.

Make PyWeakProxy a #[repr(transparent)] view sharing PyWeak's payload
(manual PyPayload with shared PAYLOAD_TYPE_ID + class check), so a proxy
is the weakref-list node itself and add()'s reuse applies to it
directly. Port CPython's insert_weakref/get_basic_refs slot discipline:
genericref at head, generic proxy right after it, with callback/class
re-verificationbefore reuse. Constructor moves to a slot_new override
since reuse returns an existing object. Drops the hidden __weakproxy
marker subclass and the proxy branch in PyWeakref_GetRef.

Assisted-by: Claude Code:claude-opus-4-8
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 7463b82b-a274-45ba-812a-ae52e78e9983

📥 Commits

Reviewing files that changed from the base of the PR and between 5150cdb and f7b36ac.

📒 Files selected for processing (1)
  • crates/vm/src/object/core.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/vm/src/object/core.rs

📝 Walkthrough

Walkthrough

Weak proxy payloads now use a transparent PyWeak representation with slot-based construction. Weakref-list insertion and reuse distinguish weakrefs from proxies, and PyWeakref_GetRef accepts only direct weakrefs.

Changes

Weakref rework

Layer / File(s) Summary
Weak proxy payload and construction
crates/vm/src/builtins/weakproxy.rs
PyWeakProxy stores a transparent PyWeak payload, constructs through slot_new, returns the underlying weak payload from helpers, and upgrades it directly.
Weakref-list reuse and downgrade wiring
crates/vm/src/object/core.rs
Weakref reuse uses guarded refcount increments, distinguishes proxy types, rechecks concurrent insertions, and passes proxy classification through downgrade paths.
Direct weakref C API extraction
crates/capi/src/weakrefobject.rs
PyWeakref_GetRef upgrades direct PyWeak objects and rejects weak proxies with TypeError.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PyObject
  participant WeakRefList
  participant PyWeak
  participant PyWeakref_GetRef
  PyObject->>WeakRefList: request weakref or weakproxy with classification
  WeakRefList->>PyWeak: reuse existing payload or allocate and insert
  WeakRefList-->>PyObject: return weakref payload
  PyWeakref_GetRef->>PyWeak: upgrade direct PyWeak
  PyWeakref_GetRef-->>PyObject: return referent or TypeError
Loading

Possibly related PRs

Suggested reviewers: youknowone

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: weakref proxy objects are now reused.
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 unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] lib: cpython/Lib/weakref.py
[x] lib: cpython/Lib/_weakrefset.py
[x] test: cpython/Lib/test/test_weakref.py (TODO: 10)
[x] test: cpython/Lib/test/test_weakset.py

dependencies:

  • weakref

dependent tests: (222 tests)

  • weakref: test_array test_ast test_asyncio test_code test_concurrent_futures test_context test_contextlib test_copy test_ctypes test_deque test_descr test_dict test_enum test_exceptions test_file test_fileio test_finalization test_frame test_functools test_gc test_generators test_genericalias test_importlib test_inspect test_io test_ipaddress test_itertools test_logging test_memoryio test_memoryview test_mmap test_ordered_dict test_pickle test_picklebuffer test_queue test_re test_scope test_set test_slice test_socket test_sqlite3 test_ssl test_struct test_sys test_tempfile test_thread test_threading test_threading_local test_type_params test_types test_typing test_unittest test_uuid test_weakref test_weakset test_xml_etree
    • asyncio: test_asyncio test_external_inspection test_os test_pdb test_unittest
    • bdb: test_bdb
    • concurrent.futures.process: test_compileall test_concurrent_futures
    • copy: test_bytes test_codecs test_collections test_copyreg test_coroutines test_csv test_decimal test_defaultdict test_dictviews test_email test_fractions test_http_cookies test_minidom test_opcache test_optparse test_platform test_plistlib test_posix test_site test_statistics test_structseq test_super test_sysconfig test_tomllib test_urllib2 test_xml_dom_minicompat test_zlib
      • argparse: test_argparse
      • collections: test_annotationlib test_bisect test_builtin test_c_locale_coercion test_call test_configparser test_contains test_ctypes test_embed test_exception_group test_fileinput test_funcattrs test_hash test_httpservers test_iter test_iterlen test_json test_math test_monitoring test_pathlib test_patma test_pprint test_pydoc test_random test_reprlib test_richcmp test_shelve test_sqlite3 test_string test_traceback test_tuple test_urllib test_userdict test_userlist test_userstring test_with
      • dataclasses: test__colorize test_ctypes test_regrtest test_zoneinfo
      • email.generator: test_email
      • gettext: test_gettext test_tools
      • http.cookiejar: test_http_cookiejar
      • http.server: test_robotparser test_urllib2_localnet test_xmlrpc
      • logging.handlers: test_pkgutil
      • mailbox: test_mailbox
      • smtplib: test_smtplib test_smtpnet
      • tarfile: test_shutil test_tarfile
      • webbrowser: test_webbrowser
    • inspect: test_abc test_asyncgen test_buffer test_clinic test_grammar test_ntpath test_operator test_posixpath test_signal test_turtle test_type_annotations test_yield_from test_zipimport test_zipimport_support
      • ast: test_compile test_compiler_codegen test_dis test_fstring test_future_stmt test_peepholer test_peg_generator test_type_comments test_ucn test_unparse
      • cmd: test_cmd
      • importlib.metadata: test_importlib
      • pkgutil: test_pyrepl test_runpy
      • rlcompleter: test_pyrepl test_rlcompleter
      • trace: test_trace
    • logging: test_hashlib test_support test_urllib2net
      • hashlib: test_hmac test_unicodedata
      • multiprocessing.util: test_concurrent_futures
      • venv: test_venv
    • multiprocessing: test_fcntl test_multiprocessing_main_handling
    • symtable: test_symtable
    • tempfile: test_bz2 test_cmd_line test_cprofile test_ctypes test_doctest test_ensurepip test_faulthandler test_filecmp test_generated_cases test_importlib test_launcher test_linecache test_modulefinder test_peg_generator test_pkg test_pstats test_py_compile test_pyrepl test_selectors test_string_literals test_subprocess test_sys_settrace test_tabnanny test_termios test_threadedtempfile test_tokenize test_urllib_response test_winconsoleio test_zipapp test_zipfile test_zipfile64 test_zstd
      • ctypes.util: test_ctypes
      • urllib.request: test_sax test_urllibnet

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@moreal moreal added the z-ca-2026 Tag to track Contribution Academy 2026 label Jul 17, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 `@crates/vm/src/object/core.rs`:
- Around line 668-675: Update the basic-proxy lookup in the after-slot logic and
the corresponding path around the referenced later code to call
find_generic_proxy_ptr with the canonical weakproxy type instead of None. Ensure
the candidate’s class is validated so callback-less weakref subclasses are not
treated as the basic proxy, while preserving the existing fallback to
self.generic.
🪄 Autofix (Beta)

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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: c8aa6465-869b-489b-b025-143443b1a9a7

📥 Commits

Reviewing files that changed from the base of the PR and between 1205fd2 and 8a9e505.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_weakref.py is excluded by !Lib/**
📒 Files selected for processing (3)
  • crates/capi/src/weakrefobject.rs
  • crates/vm/src/builtins/weakproxy.rs
  • crates/vm/src/object/core.rs
💤 Files with no reviewable changes (1)
  • crates/capi/src/weakrefobject.rs

Comment thread crates/vm/src/object/core.rs
@rlaisqls rlaisqls changed the title weakref: reuse callback-less proxy objects like CPython weakref: reuse proxy objects Jul 17, 2026

@ShaharNaveh ShaharNaveh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

tysm!

@youknowone
youknowone merged commit c7c67fd into RustPython:main Jul 18, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

z-ca-2026 Tag to track Contribution Academy 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants