fix finalizing and atexit timing (#6626)

* fix finalizing and atexit timing

* fix shutdown
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-01-18 01:54:16 +09:00
1 parent 8ce5f49908
commit 11c9b0e783
3 files changed
+83 -37

No files matched your search

-2
View File
@@ -834,8 +834,6 @@ class CWCmdLineTests(WCmdLineTests, unittest.TestCase):
class PyWCmdLineTests(WCmdLineTests, unittest.TestCase):
module = py_warnings
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_improper_option(self):
# Same as above, but check that the message is printed out when
# the interpreter is executed. This also checks that options are
+20 -35
View File
@@ -110,10 +110,11 @@ impl Interpreter {
/// Finalize vm and turns an exception to exit code.
///
/// Finalization steps including 5 steps:
/// Finalization steps (matching Py_FinalizeEx):
/// 1. Flush stdout and stderr.
/// 1. Handle exit exception and turn it to exit code.
/// 1. Wait for non-daemon threads (threading._shutdown).
/// 1. Wait for thread shutdown (call threading._shutdown).
/// 1. Mark vm as finalizing.
/// 1. Run atexit exit functions.
/// 1. Mark vm as finalized.
///
@@ -129,13 +130,26 @@ impl Interpreter {
0
};
// Wait for non-daemon threads (wait_for_thread_shutdown)
wait_for_thread_shutdown(vm);
atexit::_run_exitfuncs(vm);
// Wait for thread shutdown - call threading._shutdown() if available.
// This waits for all non-daemon threads to complete.
// threading module may not be imported, so ignore import errors.
if let Ok(threading) = vm.import("threading", 0)
&& let Ok(shutdown) = threading.get_attr("_shutdown", vm)
&& let Err(e) = shutdown.call((), vm)
{
vm.run_unraisable(
e,
Some("Exception ignored in threading shutdown".to_owned()),
threading,
);
}
// Mark as finalizing AFTER thread shutdown
vm.state.finalizing.store(true, Ordering::Release);
// Run atexit exit functions
atexit::_run_exitfuncs(vm);
vm.flush_std();
exit_code
@@ -143,35 +157,6 @@ impl Interpreter {
}
}
/// Wait until threading._shutdown completes, provided
/// the threading module was imported in the first place.
/// The shutdown routine will wait until all non-daemon
/// "threading" threads have completed.
fn wait_for_thread_shutdown(vm: &VirtualMachine) {
// Try to get the threading module if it was already imported
// Use sys.modules.get("threading") like PyImport_GetModule
let threading = match (|| -> PyResult<_> {
let sys_modules = vm.sys_module.get_attr("modules", vm)?;
let threading = sys_modules.get_item("threading", vm)?;
Ok(threading)
})() {
Ok(module) => module,
Err(_) => {
// threading not imported, nothing to do
return;
}
};
// Call threading._shutdown()
if let Err(e) = vm.call_method(&threading, "_shutdown", ()) {
vm.run_unraisable(
e,
Some("Exception ignored on threading shutdown".to_owned()),
threading,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
+63
View File
@@ -550,6 +550,18 @@ impl VirtualMachine {
#[cold]
pub fn run_unraisable(&self, e: PyBaseExceptionRef, msg: Option<String>, object: PyObjectRef) {
// During interpreter finalization, sys.unraisablehook may not be available,
// but we still need to report exceptions (especially from atexit callbacks).
// Write directly to stderr like PyErr_FormatUnraisable.
if self
.state
.finalizing
.load(std::sync::atomic::Ordering::Acquire)
{
self.write_unraisable_to_stderr(&e, msg.as_deref(), &object);
return;
}
let sys_module = self.import("sys", 0).unwrap();
let unraisablehook = sys_module.get_attr("unraisablehook", self).unwrap();
@@ -568,6 +580,57 @@ impl VirtualMachine {
}
}
/// Write unraisable exception to stderr during finalization.
/// Similar to _PyErr_WriteUnraisableDefaultHook in CPython.
fn write_unraisable_to_stderr(
&self,
e: &PyBaseExceptionRef,
msg: Option<&str>,
object: &PyObjectRef,
) {
// Get stderr once and reuse it
let stderr = crate::stdlib::sys::get_stderr(self).ok();
let write_to_stderr = |s: &str, stderr: &Option<PyObjectRef>, vm: &VirtualMachine| {
if let Some(stderr) = stderr {
let _ = vm.call_method(stderr, "write", (s.to_owned(),));
} else {
eprint!("{}", s);
}
};
// Format: "Exception ignored {msg} {object_repr}\n"
if let Some(msg) = msg {
write_to_stderr(&format!("Exception ignored {}", msg), &stderr, self);
} else {
write_to_stderr("Exception ignored in: ", &stderr, self);
}
if let Ok(repr) = object.repr(self) {
write_to_stderr(&format!("{}\n", repr.as_str()), &stderr, self);
} else {
write_to_stderr("<object repr failed>\n", &stderr, self);
}
// Write exception type and message
let exc_type_name = e.class().name();
if let Ok(exc_str) = e.as_object().str(self) {
let exc_str = exc_str.as_str();
if exc_str.is_empty() {
write_to_stderr(&format!("{}\n", exc_type_name), &stderr, self);
} else {
write_to_stderr(&format!("{}: {}\n", exc_type_name, exc_str), &stderr, self);
}
} else {
write_to_stderr(&format!("{}\n", exc_type_name), &stderr, self);
}
// Flush stderr to ensure output is visible
if let Some(ref stderr) = stderr {
let _ = self.call_method(stderr, "flush", ());
}
}
#[inline(always)]
pub fn run_frame(&self, frame: FrameRef) -> PyResult {
match self.with_frame(frame, |f| f.run(self))? {