@@ -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