@@ -12,7 +12,6 @@ mod decl {
1212 use std:: time:: Duration ;
1313
1414 static ENABLED : AtomicBool = AtomicBool :: new ( false ) ;
15- #[ allow( dead_code) ]
1615 static FATAL_ERROR_FD : AtomicI32 = AtomicI32 :: new ( 2 ) ; // stderr by default
1716
1817 // Watchdog thread state for dump_traceback_later
@@ -28,16 +27,57 @@ mod decl {
2827 type WatchdogHandle = Arc < ( Mutex < WatchdogState > , Condvar ) > ;
2928 static WATCHDOG : Mutex < Option < WatchdogHandle > > = Mutex :: new ( None ) ;
3029
31- // Fatal signal numbers that should use enable() instead
30+ // Number of fatal signals we handle
31+ #[ cfg( unix) ]
32+ const NUM_FATAL_SIGNALS : usize = 5 ;
33+ #[ cfg( windows) ]
34+ const NUM_FATAL_SIGNALS : usize = 3 ;
35+
36+ // Fatal signals to handle (with names for error messages)
3237 #[ cfg( unix) ]
33- const FATAL_SIGNALS : & [ i32 ] = & [
34- libc:: SIGBUS ,
35- libc:: SIGILL ,
36- libc:: SIGFPE ,
37- libc:: SIGABRT ,
38- libc:: SIGSEGV ,
38+ const FATAL_SIGNALS : [ ( libc:: c_int , & str ) ; NUM_FATAL_SIGNALS ] = [
39+ ( libc:: SIGBUS , "Bus error" ) ,
40+ ( libc:: SIGILL , "Illegal instruction" ) ,
41+ ( libc:: SIGFPE , "Floating-point exception" ) ,
42+ ( libc:: SIGABRT , "Aborted" ) ,
43+ ( libc:: SIGSEGV , "Segmentation fault" ) ,
44+ ] ;
45+
46+ #[ cfg( windows) ]
47+ const FATAL_SIGNALS : [ ( libc:: c_int , & str ) ; NUM_FATAL_SIGNALS ] = [
48+ ( libc:: SIGFPE , "Floating-point exception" ) ,
49+ ( libc:: SIGABRT , "Aborted" ) ,
50+ ( libc:: SIGSEGV , "Segmentation fault" ) ,
3951 ] ;
4052
53+ // Storage for previous signal handlers (Unix)
54+ #[ cfg( unix) ]
55+ static mut PREVIOUS_HANDLERS : [ libc:: sigaction ; NUM_FATAL_SIGNALS ] =
56+ unsafe { std:: mem:: zeroed ( ) } ;
57+
58+ /// Signal-safe write function - no memory allocation
59+ #[ cfg( all( not( target_arch = "wasm32" ) , not( windows) ) ) ]
60+ fn write_str_noraise ( fd : i32 , s : & str ) {
61+ unsafe {
62+ libc:: write (
63+ fd as libc:: c_int ,
64+ s. as_ptr ( ) as * const libc:: c_void ,
65+ s. len ( ) ,
66+ ) ;
67+ }
68+ }
69+
70+ #[ cfg( windows) ]
71+ fn write_str_noraise ( fd : i32 , s : & str ) {
72+ unsafe {
73+ libc:: write (
74+ fd as libc:: c_int ,
75+ s. as_ptr ( ) as * const libc:: c_void ,
76+ s. len ( ) as u32 ,
77+ ) ;
78+ }
79+ }
80+
4181 const MAX_FUNCTION_NAME_LEN : usize = 500 ;
4282
4383 fn truncate_name ( name : & str ) -> String {
@@ -122,37 +162,147 @@ mod decl {
122162 ENABLED . store ( true , Ordering :: Relaxed ) ;
123163
124164 // Install signal handlers for fatal errors
125- #[ cfg( not ( target_arch = "wasm32" ) ) ]
165+ #[ cfg( any ( unix , windows ) ) ]
126166 {
127167 install_fatal_handlers ( vm) ;
128168 }
129169
130170 Ok ( ( ) )
131171 }
132172
133- #[ cfg( not( target_arch = "wasm32" ) ) ]
173+ /// Unix signal handler for fatal errors
174+ #[ cfg( unix) ]
175+ extern "C" fn faulthandler_fatal_error ( signum : libc:: c_int ) {
176+ // Reentrant protection
177+ static REENTRANT : AtomicBool = AtomicBool :: new ( false ) ;
178+ if REENTRANT . swap ( true , Ordering :: SeqCst ) {
179+ return ;
180+ }
181+
182+ let fd = FATAL_ERROR_FD . load ( Ordering :: Relaxed ) ;
183+
184+ // Find and print signal name
185+ let signal_name = FATAL_SIGNALS
186+ . iter ( )
187+ . find ( |( sig, _) | * sig == signum)
188+ . map ( |( _, name) | * name)
189+ . unwrap_or ( "Unknown signal" ) ;
190+
191+ write_str_noraise ( fd, "\n Fatal Python error: " ) ;
192+ write_str_noraise ( fd, signal_name) ;
193+ write_str_noraise ( fd, "\n \n " ) ;
194+
195+ // Restore previous handler
196+ if let Some ( idx) = FATAL_SIGNALS . iter ( ) . position ( |( sig, _) | * sig == signum) {
197+ unsafe {
198+ libc:: sigaction ( signum, & PREVIOUS_HANDLERS [ idx] , std:: ptr:: null_mut ( ) ) ;
199+ }
200+ }
201+
202+ REENTRANT . store ( false , Ordering :: SeqCst ) ;
203+
204+ // Re-raise signal to trigger default behavior (core dump, etc.)
205+ unsafe {
206+ libc:: raise ( signum) ;
207+ }
208+ }
209+
210+ #[ cfg( unix) ]
134211 fn install_fatal_handlers ( _vm : & VirtualMachine ) {
135- // TODO: Install actual signal handlers for SIGSEGV, SIGFPE, etc.
136- // This requires careful handling because signal handlers have limited capabilities.
137- // For now, this is a placeholder that marks the module as enabled.
212+ for ( idx, ( signum, _) ) in FATAL_SIGNALS . iter ( ) . enumerate ( ) {
213+ let mut action: libc:: sigaction = unsafe { std:: mem:: zeroed ( ) } ;
214+ action. sa_sigaction = faulthandler_fatal_error as usize ;
215+ action. sa_flags = libc:: SA_NODEFER ;
216+
217+ unsafe {
218+ libc:: sigaction ( * signum, & action, & mut PREVIOUS_HANDLERS [ idx] ) ;
219+ }
220+ }
221+ }
222+
223+ /// Windows signal handler for fatal errors (simpler than VEH)
224+ #[ cfg( windows) ]
225+ extern "C" fn faulthandler_fatal_error_windows ( signum : libc:: c_int ) {
226+ // Reentrant protection
227+ static REENTRANT : AtomicBool = AtomicBool :: new ( false ) ;
228+ if REENTRANT . swap ( true , Ordering :: SeqCst ) {
229+ return ;
230+ }
231+
232+ let fd = FATAL_ERROR_FD . load ( Ordering :: Relaxed ) ;
233+
234+ // Find and print signal name
235+ let signal_name = FATAL_SIGNALS
236+ . iter ( )
237+ . find ( |( sig, _) | * sig == signum)
238+ . map ( |( _, name) | * name)
239+ . unwrap_or ( "Unknown signal" ) ;
240+
241+ write_str_noraise ( fd, "\n Fatal Python error: " ) ;
242+ write_str_noraise ( fd, signal_name) ;
243+ write_str_noraise ( fd, "\n \n " ) ;
244+
245+ REENTRANT . store ( false , Ordering :: SeqCst ) ;
246+
247+ // For SIGSEGV on Windows, need to loop raise
248+ if signum == libc:: SIGSEGV {
249+ loop {
250+ unsafe {
251+ libc:: raise ( signum) ;
252+ }
253+ }
254+ } else {
255+ unsafe {
256+ libc:: raise ( signum) ;
257+ }
258+ }
259+ }
260+
261+ #[ cfg( windows) ]
262+ static mut PREVIOUS_HANDLERS_WIN : [ libc:: sighandler_t ; NUM_FATAL_SIGNALS ] =
263+ [ 0 ; NUM_FATAL_SIGNALS ] ;
264+
265+ #[ cfg( windows) ]
266+ fn install_fatal_handlers ( _vm : & VirtualMachine ) {
267+ for ( idx, ( signum, _) ) in FATAL_SIGNALS . iter ( ) . enumerate ( ) {
268+ unsafe {
269+ PREVIOUS_HANDLERS_WIN [ idx] = libc:: signal (
270+ * signum,
271+ faulthandler_fatal_error_windows as libc:: sighandler_t ,
272+ ) ;
273+ }
274+ }
138275 }
139276
140277 #[ pyfunction]
141278 fn disable ( ) -> bool {
142279 let was_enabled = ENABLED . swap ( false , Ordering :: Relaxed ) ;
143280
144281 // Restore default signal handlers
145- #[ cfg( not ( target_arch = "wasm32" ) ) ]
282+ #[ cfg( any ( unix , windows ) ) ]
146283 {
147284 uninstall_fatal_handlers ( ) ;
148285 }
149286
150287 was_enabled
151288 }
152289
153- #[ cfg( not( target_arch = "wasm32" ) ) ]
290+ #[ cfg( unix) ]
291+ fn uninstall_fatal_handlers ( ) {
292+ for ( idx, ( signum, _) ) in FATAL_SIGNALS . iter ( ) . enumerate ( ) {
293+ unsafe {
294+ libc:: sigaction ( * signum, & PREVIOUS_HANDLERS [ idx] , std:: ptr:: null_mut ( ) ) ;
295+ }
296+ }
297+ }
298+
299+ #[ cfg( windows) ]
154300 fn uninstall_fatal_handlers ( ) {
155- // TODO: Restore original signal handlers
301+ for ( idx, ( signum, _) ) in FATAL_SIGNALS . iter ( ) . enumerate ( ) {
302+ unsafe {
303+ libc:: signal ( * signum, PREVIOUS_HANDLERS_WIN [ idx] ) ;
304+ }
305+ }
156306 }
157307
158308 #[ pyfunction]
@@ -251,6 +401,15 @@ mod decl {
251401 #[ cfg( not( target_arch = "wasm32" ) ) ]
252402 {
253403 let header_bytes = header. as_bytes ( ) ;
404+ #[ cfg( windows) ]
405+ unsafe {
406+ libc:: write (
407+ fd,
408+ header_bytes. as_ptr ( ) as * const libc:: c_void ,
409+ header_bytes. len ( ) as u32 ,
410+ ) ;
411+ }
412+ #[ cfg( not( windows) ) ]
254413 unsafe {
255414 libc:: write (
256415 fd,
@@ -263,6 +422,11 @@ mod decl {
263422 // because we don't have access to the VM's frame stack.
264423 // Just output a message indicating timeout occurred.
265424 let msg = b"<timeout: cannot dump traceback from watchdog thread>\n " ;
425+ #[ cfg( windows) ]
426+ unsafe {
427+ libc:: write ( fd, msg. as_ptr ( ) as * const libc:: c_void , msg. len ( ) as u32 ) ;
428+ }
429+ #[ cfg( not( windows) ) ]
266430 unsafe {
267431 libc:: write ( fd, msg. as_ptr ( ) as * const libc:: c_void , msg. len ( ) ) ;
268432 }
@@ -473,7 +637,7 @@ mod decl {
473637 #[ cfg( unix) ]
474638 fn check_signum ( signum : i32 , vm : & VirtualMachine ) -> PyResult < ( ) > {
475639 // Check if it's a fatal signal
476- if FATAL_SIGNALS . contains ( & signum) {
640+ if FATAL_SIGNALS . iter ( ) . any ( | ( sig , _ ) | * sig == signum) {
477641 return Err ( vm. new_runtime_error ( format ! (
478642 "signal {} cannot be registered, use enable() instead" ,
479643 signum
0 commit comments