mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Fix thread teardown panic when weakref callback fires during cleanup (#7965)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -27,4 +27,4 @@ Lib/site-packages/*
|
||||
Lib/test/data/*
|
||||
!Lib/test/data/README
|
||||
cpython/
|
||||
|
||||
.claude/scheduled_tasks.lock
|
||||
@@ -530,10 +530,16 @@ pub(crate) mod _thread {
|
||||
// Increment thread count when thread actually starts executing
|
||||
vm.state.thread_count.fetch_add(1);
|
||||
|
||||
match func.invoke(args, vm) {
|
||||
Ok(_obj) => {}
|
||||
Err(e) if e.fast_isinstance(vm.ctx.exceptions.system_exit) => {}
|
||||
Err(exc) => {
|
||||
// Inner scope: drop `func` (and its Python refs) before the thread
|
||||
// slot is torn down below. Otherwise the parameter `func` would drop
|
||||
// at end-of-function, after cleanup_current_thread_frames has cleared
|
||||
// CURRENT_THREAD_SLOT, and a weakref callback fired during that drop
|
||||
// would panic in push_thread_frame.
|
||||
{
|
||||
let func = func;
|
||||
if let Err(exc) = func.invoke(args, vm)
|
||||
&& !exc.fast_isinstance(vm.ctx.exceptions.system_exit)
|
||||
{
|
||||
vm.run_unraisable(
|
||||
exc,
|
||||
Some("Exception ignored in thread started by".to_owned()),
|
||||
@@ -1663,11 +1669,18 @@ pub(crate) mod _thread {
|
||||
// Increment thread count when thread actually starts executing
|
||||
vm_state.thread_count.fetch_add(1);
|
||||
|
||||
// Run the function
|
||||
match func.invoke((), vm) {
|
||||
Ok(_) => {}
|
||||
Err(e) if e.fast_isinstance(vm.ctx.exceptions.system_exit) => {}
|
||||
Err(exc) => {
|
||||
// Inner scope: drop `func` (and its Python refs) before the
|
||||
// outer scopeguard::defer tears down the thread slot. As a
|
||||
// `move` closure capture, `func` would otherwise drop after
|
||||
// all locals (including the scopeguard `_guard`), and a
|
||||
// weakref callback fired during that drop would panic in
|
||||
// push_thread_frame.
|
||||
{
|
||||
let func = func;
|
||||
// Run the function
|
||||
if let Err(exc) = func.invoke((), vm)
|
||||
&& !exc.fast_isinstance(vm.ctx.exceptions.system_exit)
|
||||
{
|
||||
vm.run_unraisable(
|
||||
exc,
|
||||
Some("Exception ignored in thread started by".to_owned()),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
def import_in_thread(module_name):
|
||||
@@ -62,6 +63,48 @@ def start_fork_process_after_thread():
|
||||
assert process.exitcode == 0, process.exitcode
|
||||
|
||||
|
||||
def thread_join_ordering():
|
||||
output = []
|
||||
|
||||
def thread_function(name):
|
||||
output.append((name, 0))
|
||||
time.sleep(2.0)
|
||||
output.append((name, 1))
|
||||
|
||||
output.append((0, 0))
|
||||
x = threading.Thread(target=thread_function, args=(1,))
|
||||
output.append((0, 1))
|
||||
x.start()
|
||||
output.append((0, 2))
|
||||
x.join()
|
||||
output.append((0, 3))
|
||||
|
||||
assert len(output) == 6, output
|
||||
# CPython has [(1, 0), (0, 2)] for the middle 2, but we have [(0, 2), (1, 0)]
|
||||
# TODO: maybe fix this, if it turns out to be a problem?
|
||||
# assert output == [(0, 0), (0, 1), (1, 0), (0, 2), (1, 1), (0, 3)]
|
||||
|
||||
|
||||
def thread_exit_without_join():
|
||||
# Regression for https://github.com/RustPython/RustPython/issues/7813:
|
||||
# a thread started without ``.join()`` must exit cleanly even when the
|
||||
# captured target callable drops during teardown (which can fire
|
||||
# weakref callbacks that re-enter the VM).
|
||||
output = []
|
||||
|
||||
def runner():
|
||||
output.append("runner done")
|
||||
|
||||
threading.Thread(target=runner).start()
|
||||
time.sleep(1)
|
||||
output.append("main done")
|
||||
assert "runner done" in output, output
|
||||
assert "main done" in output, output
|
||||
|
||||
|
||||
thread_join_ordering()
|
||||
thread_exit_without_join()
|
||||
|
||||
import_in_thread("functools")
|
||||
import_in_thread("tempfile")
|
||||
import_in_thread("multiprocessing.connection")
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import threading
|
||||
import time
|
||||
|
||||
output = []
|
||||
|
||||
|
||||
def thread_function(name):
|
||||
output.append((name, 0))
|
||||
time.sleep(2.0)
|
||||
output.append((name, 1))
|
||||
|
||||
|
||||
output.append((0, 0))
|
||||
x = threading.Thread(target=thread_function, args=(1,))
|
||||
output.append((0, 1))
|
||||
x.start()
|
||||
output.append((0, 2))
|
||||
x.join()
|
||||
output.append((0, 3))
|
||||
|
||||
assert len(output) == 6, output
|
||||
# CPython has [(1, 0), (0, 2)] for the middle 2, but we have [(0, 2), (1, 0)]
|
||||
# TODO: maybe fix this, if it turns out to be a problem?
|
||||
# assert output == [(0, 0), (0, 1), (1, 0), (0, 2), (1, 1), (0, 3)]
|
||||
Reference in New Issue
Block a user