Skip to content

Commit 785eddf

Browse files
committed
fix atexit
1 parent 346519b commit 785eddf

File tree

3 files changed

+28
-9
lines changed

3 files changed

+28
-9
lines changed

Lib/test/test_threading.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,8 +1008,6 @@ def test_1_join_on_shutdown(self):
10081008

10091009
@unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
10101010
@unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
1011-
# TODO: RUSTPYTHON need to fix test_1_join_on_shutdown then this might work
1012-
@unittest.expectedFailure
10131011
def test_2_join_in_forked_process(self):
10141012
# Like the test above, but from a forked interpreter
10151013
script = """if 1:
@@ -1730,8 +1728,6 @@ def worker(started, cont, interrupted):
17301728

17311729
class AtexitTests(unittest.TestCase):
17321730

1733-
# TODO: RUSTPYTHON
1734-
@unittest.expectedFailure
17351731
def test_atexit_output(self):
17361732
rc, out, err = assert_python_ok("-c", """if True:
17371733
import threading

crates/vm/src/vm/interpreter.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,12 @@ impl Interpreter {
110110

111111
/// Finalize vm and turns an exception to exit code.
112112
///
113-
/// Finalization steps including 4 steps:
113+
/// Finalization steps (matching Py_FinalizeEx):
114114
/// 1. Flush stdout and stderr.
115-
/// 1. Handle exit exception and turn it to exit code.
116-
/// 1. Run atexit exit functions.
117-
/// 1. Mark vm as finalized.
115+
/// 2. Handle exit exception and turn it to exit code.
116+
/// 3. Wait for thread shutdown (call threading._shutdown).
117+
/// 4. Mark vm as finalizing.
118+
/// 5. Run atexit exit functions.
118119
///
119120
/// Note that calling `finalize` is not necessary by purpose though.
120121
pub fn finalize(self, exc: Option<PyBaseExceptionRef>) -> u32 {
@@ -128,10 +129,21 @@ impl Interpreter {
128129
0
129130
};
130131

131-
atexit::_run_exitfuncs(vm);
132+
// Wait for thread shutdown - call threading._shutdown() if available.
133+
// This waits for all non-daemon threads to complete.
134+
// threading module may not be imported, so ignore import errors.
135+
if let Ok(threading) = vm.import("threading", 0)
136+
&& let Ok(shutdown) = threading.get_attr("_shutdown", vm)
137+
{
138+
let _ = shutdown.call((), vm);
139+
}
132140

141+
// Mark as finalizing AFTER thread shutdown (matches CPython)
133142
vm.state.finalizing.store(true, Ordering::Release);
134143

144+
// Run atexit exit functions
145+
atexit::_run_exitfuncs(vm);
146+
135147
vm.flush_std();
136148

137149
exit_code

crates/vm/src/vm/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,17 @@ impl VirtualMachine {
472472

473473
#[cold]
474474
pub fn run_unraisable(&self, e: PyBaseExceptionRef, msg: Option<String>, object: PyObjectRef) {
475+
// Suppress unraisable exceptions during interpreter finalization.
476+
// This matches CPython behavior where daemon thread exceptions and
477+
// __del__ errors are silently ignored during shutdown.
478+
if self
479+
.state
480+
.finalizing
481+
.load(std::sync::atomic::Ordering::Acquire)
482+
{
483+
return;
484+
}
485+
475486
let sys_module = self.import("sys", 0).unwrap();
476487
let unraisablehook = sys_module.get_attr("unraisablehook", self).unwrap();
477488

0 commit comments

Comments
 (0)