Skip to content

Commit 0b8ce97

Browse files
committed
fix shutdown
1 parent cdf4c6f commit 0b8ce97

File tree

2 files changed

+61
-33
lines changed

2 files changed

+61
-33
lines changed

crates/vm/src/vm/interpreter.rs

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,13 @@ impl Interpreter {
135135
// threading module may not be imported, so ignore import errors.
136136
if let Ok(threading) = vm.import("threading", 0)
137137
&& let Ok(shutdown) = threading.get_attr("_shutdown", vm)
138+
&& let Err(e) = shutdown.call((), vm)
138139
{
139-
let _ = shutdown.call((), vm);
140+
vm.run_unraisable(
141+
e,
142+
Some("Exception ignored in threading shutdown".to_owned()),
143+
threading,
144+
);
140145
}
141146

142147
// Mark as finalizing AFTER thread shutdown
@@ -152,35 +157,6 @@ impl Interpreter {
152157
}
153158
}
154159

155-
/// Wait until threading._shutdown completes, provided
156-
/// the threading module was imported in the first place.
157-
/// The shutdown routine will wait until all non-daemon
158-
/// "threading" threads have completed.
159-
fn wait_for_thread_shutdown(vm: &VirtualMachine) {
160-
// Try to get the threading module if it was already imported
161-
// Use sys.modules.get("threading") like PyImport_GetModule
162-
let threading = match (|| -> PyResult<_> {
163-
let sys_modules = vm.sys_module.get_attr("modules", vm)?;
164-
let threading = sys_modules.get_item("threading", vm)?;
165-
Ok(threading)
166-
})() {
167-
Ok(module) => module,
168-
Err(_) => {
169-
// threading not imported, nothing to do
170-
return;
171-
}
172-
};
173-
174-
// Call threading._shutdown()
175-
if let Err(e) = vm.call_method(&threading, "_shutdown", ()) {
176-
vm.run_unraisable(
177-
e,
178-
Some("Exception ignored on threading shutdown".to_owned()),
179-
threading,
180-
);
181-
}
182-
}
183-
184160
#[cfg(test)]
185161
mod tests {
186162
use super::*;

crates/vm/src/vm/mod.rs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -550,14 +550,15 @@ impl VirtualMachine {
550550

551551
#[cold]
552552
pub fn run_unraisable(&self, e: PyBaseExceptionRef, msg: Option<String>, object: PyObjectRef) {
553-
// Suppress unraisable exceptions during interpreter finalization.
554-
// when daemon thread exceptions and
555-
// __del__ errors are silently ignored during shutdown.
553+
// During interpreter finalization, sys.unraisablehook may not be available,
554+
// but we still need to report exceptions (especially from atexit callbacks).
555+
// Write directly to stderr like PyErr_FormatUnraisable.
556556
if self
557557
.state
558558
.finalizing
559559
.load(std::sync::atomic::Ordering::Acquire)
560560
{
561+
self.write_unraisable_to_stderr(&e, msg.as_deref(), &object);
561562
return;
562563
}
563564

@@ -579,6 +580,57 @@ impl VirtualMachine {
579580
}
580581
}
581582

583+
/// Write unraisable exception to stderr during finalization.
584+
/// Similar to _PyErr_WriteUnraisableDefaultHook in CPython.
585+
fn write_unraisable_to_stderr(
586+
&self,
587+
e: &PyBaseExceptionRef,
588+
msg: Option<&str>,
589+
object: &PyObjectRef,
590+
) {
591+
// Get stderr once and reuse it
592+
let stderr = crate::stdlib::sys::get_stderr(self).ok();
593+
594+
let write_to_stderr = |s: &str, stderr: &Option<PyObjectRef>, vm: &VirtualMachine| {
595+
if let Some(stderr) = stderr {
596+
let _ = vm.call_method(stderr, "write", (s.to_owned(),));
597+
} else {
598+
eprint!("{}", s);
599+
}
600+
};
601+
602+
// Format: "Exception ignored {msg} {object_repr}\n"
603+
if let Some(msg) = msg {
604+
write_to_stderr(&format!("Exception ignored {}", msg), &stderr, self);
605+
} else {
606+
write_to_stderr("Exception ignored in: ", &stderr, self);
607+
}
608+
609+
if let Ok(repr) = object.repr(self) {
610+
write_to_stderr(&format!("{}\n", repr.as_str()), &stderr, self);
611+
} else {
612+
write_to_stderr("<object repr failed>\n", &stderr, self);
613+
}
614+
615+
// Write exception type and message
616+
let exc_type_name = e.class().name();
617+
if let Ok(exc_str) = e.as_object().str(self) {
618+
let exc_str = exc_str.as_str();
619+
if exc_str.is_empty() {
620+
write_to_stderr(&format!("{}\n", exc_type_name), &stderr, self);
621+
} else {
622+
write_to_stderr(&format!("{}: {}\n", exc_type_name, exc_str), &stderr, self);
623+
}
624+
} else {
625+
write_to_stderr(&format!("{}\n", exc_type_name), &stderr, self);
626+
}
627+
628+
// Flush stderr to ensure output is visible
629+
if let Some(ref stderr) = stderr {
630+
let _ = self.call_method(stderr, "flush", ());
631+
}
632+
}
633+
582634
#[inline(always)]
583635
pub fn run_frame(&self, frame: FrameRef) -> PyResult {
584636
match self.with_frame(frame, |f| f.run(self))? {

0 commit comments

Comments
 (0)