mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
fix finalizing and atexit timing (#6626)
* fix finalizing and atexit timing * fix shutdown
This commit is contained in:
1 parent
8ce5f49908
commit
11c9b0e783
3 files changed
+83
-37
No files matched your search
Vendored
-2
@@ -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
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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))? {
|
||||
|
||||
Reference in new issue
Block a user