Fix thread teardown panic when weakref callback fires during cleanup#7965
Fix thread teardown panic when weakref callback fires during cleanup#7965JamesClarke7283 wants to merge 6 commits into
Conversation
- Drop the target function inside an inner scope so its Python refs are released before cleanup_current_thread_frames clears CURRENT_THREAD_SLOT - Avoids panic in push_thread_frame when a weakref callback runs as `func` drops at end-of-function
|
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:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughThread-target invocations are run inside inner scopes so the Python callable drops before thread-slot/frame-tracking teardown; two regression tests verify join ordering and clean teardown for unjoined threads. ChangesThread teardown safety
Sequence Diagram(s)sequenceDiagram
participant Starter as ThreadStarter (spawn/join)
participant Runner as run_thread / start_joinable_thread
participant Func as func.invoke(...)
participant Teardown as ThreadTeardown
Starter->>Runner: spawn thread
Runner->>Func: call func.invoke(...) inside inner scope
Func-->>Teardown: func object drops (on inner-scope exit)
Teardown->>Teardown: unlock sentinel / cleanup_thread_local_data / cleanup_current_thread_frames / decrement thread_count
Starter->>Starter: join or wait for completion
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
ShaharNaveh
left a comment
There was a problem hiding this comment.
Please add a test case to extra_tests/ so we won't have regression in the future:)
Will do |
- Rewrite both match blocks as `if let Err(exc) && !is(SystemExit)` per @ShaharNaveh - Add extra_tests/snippets/test_threading_teardown.py regression test from issue RustPython#7813 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
{"subject": "Use double quotes in threading teardown test", "body": ""}
Merge the contents of test_threading.py and test_threading_teardown.py into stdlib_threading.py to match the file's helper-function style; the two source snippets are deleted. The bodies become `thread_join_ordering` (start/join ordering) and `thread_exit_without_join` (regression test for RustPython#7813 — no-join thread teardown must not panic). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Fixes #7813.
threading.Thread(target=fn).start()panics in debug builds during thread teardown:Root cause
In both
_threadspawn paths (start_new_thread→run_thread, andstart_joinable_thread's spawn closure), the userfunc: ArgCallable(holding aPyObjectRef) drops aftercleanup_current_thread_frames(vm)has setCURRENT_THREAD_SLOT = None:run_thread,funcis a function parameter — it drops at end-of-function, after the inline cleanup.start_joinable_threadspawn closure,funcis amove-capture — and closure captures drop after all locals at end-of-body, so they drop after thescopeguard::defer!guard fires the cleanup.When
funcfinally drops, the innerPyBoundMethodis freed, triggeringWeakRefList::clear→with_vm→ a Python weakref callback →push_thread_frame, which finds the slot alreadyNoneand hits thedebug_assert!.Fix
Move each
func.invoke(...)match into an inner block withlet func = func;. That movesfuncinto a local declared inside the inner scope, so it drops at the end of that scope — before the cleanup tears downCURRENT_THREAD_SLOT.Two small reorderings in
crates/vm/src/stdlib/_thread.rs. No API or lifecycle changes; thedebug_assert!inpush_thread_frame/pop_thread_framestays as a safety net.Test plan
extra_tests/snippets/test_threading.py: exit 0.python -m test test_thread(24 tests): all pass.python -m test test_threading(223 tests): all pass.python -m test test_threading_local test_threadedtempfile: all pass.cargo check -p rustpython-vm -p rustpython-stdlib: clean.Note:
extra_tests/snippets/stdlib_threading.pysegfaults on bothmainand this branch identically (an unrelated pre-existing issue in the fork/multiprocessing path) — not introduced by this change.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests
Chores