@@ -400,6 +400,138 @@ pub(crate) mod _thread {
400400 vm. state . thread_count . load ( )
401401 }
402402
403+ /// ExceptHookArgs - simple class to hold exception hook arguments
404+ /// This allows threading.py to import _excepthook and _ExceptHookArgs from _thread
405+ #[ pyattr]
406+ #[ pyclass( module = "_thread" , name = "_ExceptHookArgs" ) ]
407+ #[ derive( Debug , PyPayload ) ]
408+ struct ExceptHookArgs {
409+ exc_type : crate :: PyObjectRef ,
410+ exc_value : crate :: PyObjectRef ,
411+ exc_traceback : crate :: PyObjectRef ,
412+ thread : crate :: PyObjectRef ,
413+ }
414+
415+ #[ pyclass( with( Constructor ) ) ]
416+ impl ExceptHookArgs {
417+ #[ pygetset]
418+ fn exc_type ( & self ) -> crate :: PyObjectRef {
419+ self . exc_type . clone ( )
420+ }
421+
422+ #[ pygetset]
423+ fn exc_value ( & self ) -> crate :: PyObjectRef {
424+ self . exc_value . clone ( )
425+ }
426+
427+ #[ pygetset]
428+ fn exc_traceback ( & self ) -> crate :: PyObjectRef {
429+ self . exc_traceback . clone ( )
430+ }
431+
432+ #[ pygetset]
433+ fn thread ( & self ) -> crate :: PyObjectRef {
434+ self . thread . clone ( )
435+ }
436+ }
437+
438+ impl Constructor for ExceptHookArgs {
439+ // Takes a single iterable argument like namedtuple
440+ type Args = ( crate :: PyObjectRef , ) ;
441+
442+ fn py_new ( _cls : & Py < PyType > , args : Self :: Args , vm : & VirtualMachine ) -> PyResult < Self > {
443+ // Convert the argument to a list/tuple and extract elements
444+ let seq: Vec < crate :: PyObjectRef > = args. 0 . try_to_value ( vm) ?;
445+ if seq. len ( ) != 4 {
446+ return Err ( vm. new_type_error ( format ! (
447+ "_ExceptHookArgs expected 4 arguments, got {}" ,
448+ seq. len( )
449+ ) ) ) ;
450+ }
451+ Ok ( Self {
452+ exc_type : seq[ 0 ] . clone ( ) ,
453+ exc_value : seq[ 1 ] . clone ( ) ,
454+ exc_traceback : seq[ 2 ] . clone ( ) ,
455+ thread : seq[ 3 ] . clone ( ) ,
456+ } )
457+ }
458+ }
459+
460+ /// Handle uncaught exception in Thread.run()
461+ #[ pyfunction]
462+ fn _excepthook ( args : crate :: PyObjectRef , vm : & VirtualMachine ) -> PyResult < ( ) > {
463+ // Type check: args must be _ExceptHookArgs
464+ let args = args. downcast :: < ExceptHookArgs > ( ) . map_err ( |_| {
465+ vm. new_type_error (
466+ "_thread._excepthook argument type must be _ExceptHookArgs" . to_owned ( ) ,
467+ )
468+ } ) ?;
469+
470+ let exc_type = args. exc_type . clone ( ) ;
471+ let exc_value = args. exc_value . clone ( ) ;
472+ let exc_traceback = args. exc_traceback . clone ( ) ;
473+ let thread = args. thread . clone ( ) ;
474+
475+ // Silently ignore SystemExit (identity check)
476+ if exc_type. is ( vm. ctx . exceptions . system_exit . as_ref ( ) ) {
477+ return Ok ( ( ) ) ;
478+ }
479+
480+ // Get stderr - fall back to thread._stderr if sys.stderr is None
481+ let file = match vm. sys_module . get_attr ( "stderr" , vm) {
482+ Ok ( stderr) if !vm. is_none ( & stderr) => stderr,
483+ _ => {
484+ if vm. is_none ( & thread) {
485+ // do nothing if sys.stderr is None and thread is None
486+ return Ok ( ( ) ) ;
487+ }
488+ let thread_stderr = thread. get_attr ( "_stderr" , vm) ?;
489+ if vm. is_none ( & thread_stderr) {
490+ // do nothing if sys.stderr is None and sys.stderr was None
491+ // when the thread was created
492+ return Ok ( ( ) ) ;
493+ }
494+ thread_stderr
495+ }
496+ } ;
497+
498+ // Print "Exception in thread {thread.name}:"
499+ let thread_name = if !vm. is_none ( & thread) {
500+ thread
501+ . get_attr ( "name" , vm)
502+ . ok ( )
503+ . and_then ( |n| n. str ( vm) . ok ( ) )
504+ . map ( |s| s. as_str ( ) . to_owned ( ) )
505+ } else {
506+ None
507+ } ;
508+ let name = thread_name. unwrap_or_else ( || format ! ( "{}" , get_ident( ) ) ) ;
509+
510+ let _ = vm. call_method (
511+ & file,
512+ "write" ,
513+ ( format ! ( "Exception in thread {}:\n " , name) , ) ,
514+ ) ;
515+
516+ // Display the traceback
517+ if let Ok ( traceback_mod) = vm. import ( "traceback" , 0 )
518+ && let Ok ( print_exc) = traceback_mod. get_attr ( "print_exception" , vm)
519+ {
520+ use crate :: function:: KwArgs ;
521+ let kwargs: KwArgs = vec ! [ ( "file" . to_owned( ) , file. clone( ) ) ]
522+ . into_iter ( )
523+ . collect ( ) ;
524+ let _ = print_exc. call_with_args (
525+ crate :: function:: FuncArgs :: new ( vec ! [ exc_type, exc_value, exc_traceback] , kwargs) ,
526+ vm,
527+ ) ;
528+ }
529+
530+ // Flush file
531+ let _ = vm. call_method ( & file, "flush" , ( ) ) ;
532+ Ok ( ( ) )
533+ }
534+
403535 #[ pyattr]
404536 #[ pyclass( module = "thread" , name = "_local" ) ]
405537 #[ derive( Debug , PyPayload ) ]
0 commit comments