@@ -17,30 +17,85 @@ lazy_static! {
1717}
1818
1919#[ cfg( unix) ]
20- fn install_sigint_handler ( ) -> Result < ( ) , MultithreadedRuntimeError > {
20+ lazy_static ! {
21+ static ref SIGINT_HANDLER_REGISTRATION : RwLock <Option <signal_hook_registry:: SigId >> = RwLock :: new( None ) ;
22+ }
23+
24+ #[ cfg( windows) ]
25+ lazy_static ! {
26+ static ref SIGINT_HANDLER_INSTALLED : AtomicBool = AtomicBool :: new( false ) ;
27+ }
28+
29+ #[ cfg( unix) ]
30+ fn install_sigint_handler ( ) -> Result < signal_hook_registry:: SigId , MultithreadedRuntimeError > {
2131 use signal_hook:: consts:: SIGINT ;
22- use signal_hook:: flag;
2332
2433 // Register the SIGINT handler to set our atomic flag.
25- // Using `signal_hook::flag::register` allows us to set the atomic flag when SIGINT is received.
26- flag:: register ( SIGINT , SIGINT_DETECTED . clone ( ) ) . map_err ( |e| {
34+ // Using signal_hook_registry directly to get a SigId that we can unregister later.
35+ let flag = SIGINT_DETECTED . clone ( ) ;
36+ let signal_id = unsafe {
37+ signal_hook_registry:: register ( SIGINT , move || {
38+ flag. store ( true , Ordering :: SeqCst ) ;
39+ } )
40+ }
41+ . map_err ( |e| {
2742 MultithreadedRuntimeError :: Other ( format ! ( "Initialization Error: Unable to register SIGINT handler {e:?}" ) )
2843 } ) ?;
2944
30- Ok ( ( ) )
45+ Ok ( signal_id)
46+ }
47+
48+ #[ cfg( windows) ]
49+ extern "system" fn console_ctrl_handler (
50+ ctrl_type : winapi:: shared:: minwindef:: DWORD ,
51+ ) -> winapi:: shared:: minwindef:: BOOL {
52+ use winapi:: um:: wincon;
53+
54+ // Only handle CTRL_C_EVENT
55+ if ctrl_type == wincon:: CTRL_C_EVENT {
56+ // Check if we have active operations
57+ let has_active_ops = {
58+ let guard = MULTITHREADED_RUNTIME . read ( ) . unwrap ( ) ;
59+ if let Some ( ( runtime_pid, ref runtime) ) = * guard {
60+ runtime_pid == std:: process:: id ( ) && runtime. external_executor_count ( ) > 0
61+ } else {
62+ false
63+ }
64+ } ;
65+
66+ if has_active_ops {
67+ // We have active operations, handle it ourselves
68+ SIGINT_DETECTED . store ( true , Ordering :: SeqCst ) ;
69+ winapi:: shared:: minwindef:: TRUE
70+ } else {
71+ // No active operations, let Python's handler (or default) handle it
72+ // Return FALSE to continue handler chain
73+ winapi:: shared:: minwindef:: FALSE
74+ }
75+ } else {
76+ // For other control events, let default handler process them
77+ winapi:: shared:: minwindef:: FALSE
78+ }
3179}
3280
3381#[ cfg( windows) ]
3482fn install_sigint_handler ( ) -> Result < ( ) , MultithreadedRuntimeError > {
35- // On Windows, use ctrlc crate.
36- // This sets a callback to run on Ctrl-C:
37- let sigint_detected_flag = SIGINT_DETECTED . clone ( ) ;
38- ctrlc:: set_handler ( move || {
39- sigint_detected_flag. store ( true , Ordering :: SeqCst ) ;
40- } )
41- . map_err ( |e| {
42- MultithreadedRuntimeError :: Other ( format ! ( "Initialization Error: Unable to register SIGINT handler {e:?}" ) )
43- } ) ?;
83+ use winapi:: um:: consoleapi:: SetConsoleCtrlHandler ;
84+ use winapi:: um:: wincon:: CTRL_C_EVENT ;
85+
86+ // Install our handler using Windows API directly (instead of ctrlc)
87+ // Our handler checks if operations are active:
88+ // - If active: handles Ctrl+C and returns TRUE (stops propagation)
89+ // - If not active: returns FALSE (allows Python's handler or default to handle it)
90+ // This way we don't need to save/restore Python's handler - we just delegate to it
91+ unsafe {
92+ if SetConsoleCtrlHandler ( Some ( console_ctrl_handler) , winapi:: shared:: minwindef:: TRUE ) == 0 {
93+ let error = winapi:: um:: errhandlingapi:: GetLastError ( ) ;
94+ return Err ( MultithreadedRuntimeError :: Other ( format ! (
95+ "Initialization Error: Unable to register SIGINT handler. Windows error: {error}"
96+ ) ) ) ;
97+ }
98+ }
4499 Ok ( ( ) )
45100}
46101
@@ -54,7 +109,20 @@ fn check_sigint_handler() -> Result<(), MultithreadedRuntimeError> {
54109 let pid = std:: process:: id ( ) ;
55110
56111 if stored_pid == pid {
57- return Ok ( ( ) ) ;
112+ // Check if handler is already registered
113+ #[ cfg( unix) ]
114+ {
115+ let reg = SIGINT_HANDLER_REGISTRATION . read ( ) . unwrap ( ) ;
116+ if reg. is_some ( ) {
117+ return Ok ( ( ) ) ;
118+ }
119+ }
120+ #[ cfg( windows) ]
121+ {
122+ if SIGINT_HANDLER_INSTALLED . load ( Ordering :: SeqCst ) {
123+ return Ok ( ( ) ) ;
124+ }
125+ }
58126 }
59127
60128 // Need to install it; acquire a lock to do so.
@@ -63,10 +131,33 @@ fn check_sigint_handler() -> Result<(), MultithreadedRuntimeError> {
63131 // If another thread beat us to it while we're waiting for the lock.
64132 let stored_pid = SIGINT_HANDLER_INSTALL_PID . 0 . load ( Ordering :: SeqCst ) ;
65133 if stored_pid == pid {
66- return Ok ( ( ) ) ;
134+ #[ cfg( unix) ]
135+ {
136+ let reg = SIGINT_HANDLER_REGISTRATION . read ( ) . unwrap ( ) ;
137+ if reg. is_some ( ) {
138+ return Ok ( ( ) ) ;
139+ }
140+ }
141+ #[ cfg( windows) ]
142+ {
143+ if SIGINT_HANDLER_INSTALLED . load ( Ordering :: SeqCst ) {
144+ return Ok ( ( ) ) ;
145+ }
146+ }
147+ }
148+
149+ #[ cfg( unix) ]
150+ {
151+ let signal_id = install_sigint_handler ( ) ?;
152+ let mut reg = SIGINT_HANDLER_REGISTRATION . write ( ) . unwrap ( ) ;
153+ * reg = Some ( signal_id) ;
67154 }
68155
69- install_sigint_handler ( ) ?;
156+ #[ cfg( windows) ]
157+ {
158+ install_sigint_handler ( ) ?;
159+ SIGINT_HANDLER_INSTALLED . store ( true , Ordering :: SeqCst ) ;
160+ }
70161
71162 // Finally, store that we have installed it successfully.
72163 SIGINT_HANDLER_INSTALL_PID . 0 . store ( pid, Ordering :: SeqCst ) ;
@@ -94,6 +185,65 @@ fn in_sigint_shutdown() -> bool {
94185 SIGINT_DETECTED . load ( Ordering :: Relaxed )
95186}
96187
188+ #[ cfg( unix) ]
189+ fn restore_sigint_handler ( ) -> Result < ( ) , MultithreadedRuntimeError > {
190+ use signal_hook_registry;
191+
192+ let mut reg = SIGINT_HANDLER_REGISTRATION . write ( ) . unwrap ( ) ;
193+ if let Some ( signal_id) = reg. take ( ) {
194+ signal_hook_registry:: unregister ( signal_id) ;
195+ }
196+ Ok ( ( ) )
197+ }
198+
199+ #[ cfg( windows) ]
200+ fn restore_sigint_handler ( ) -> Result < ( ) , MultithreadedRuntimeError > {
201+ use winapi:: um:: consoleapi:: SetConsoleCtrlHandler ;
202+
203+ // Remove our handler by unregistering it
204+ // This allows Python's handler (if any) or the default handler to take over
205+ unsafe {
206+ if SetConsoleCtrlHandler ( Some ( console_ctrl_handler) , winapi:: shared:: minwindef:: FALSE ) == 0 {
207+ let error = winapi:: um:: errhandlingapi:: GetLastError ( ) ;
208+ // Log but don't fail - handler removal is best effort
209+ if cfg ! ( debug_assertions) {
210+ eprintln ! ( "[debug] Warning: Failed to unregister console control handler. Windows error: {error}" ) ;
211+ }
212+ }
213+ }
214+ SIGINT_HANDLER_INSTALLED . store ( false , Ordering :: SeqCst ) ;
215+ Ok ( ( ) )
216+ }
217+
218+ fn maybe_restore_sigint_handler ( ) -> Result < ( ) , MultithreadedRuntimeError > {
219+ // Check if runtime exists and has no active operations
220+ let guard = MULTITHREADED_RUNTIME . read ( ) . unwrap ( ) ;
221+ if let Some ( ( runtime_pid, ref runtime) ) = * guard
222+ && runtime_pid == std:: process:: id ( )
223+ && runtime. external_executor_count ( ) == 0
224+ {
225+ // Check if handler is installed
226+ let should_restore = {
227+ #[ cfg( unix) ]
228+ {
229+ let reg = SIGINT_HANDLER_REGISTRATION . read ( ) . unwrap ( ) ;
230+ reg. is_some ( )
231+ }
232+ #[ cfg( windows) ]
233+ {
234+ SIGINT_HANDLER_INSTALLED . load ( Ordering :: SeqCst )
235+ }
236+ } ;
237+
238+ if should_restore {
239+ // No active operations, restore handler
240+ drop ( guard) ;
241+ restore_sigint_handler ( ) ?;
242+ }
243+ }
244+ Ok ( ( ) )
245+ }
246+
97247fn signal_check_background_loop ( ) {
98248 const SIGNAL_CHECK_INTERVAL : Duration = Duration :: from_millis ( 250 ) ;
99249
@@ -223,6 +373,10 @@ where
223373 return Err ( PyKeyboardInterrupt :: new_err ( ( ) ) ) ;
224374 }
225375
376+ // After operation completes, check if we should restore the signal handler
377+ // This allows Python's original handler to be restored when no operations are active
378+ let _ = maybe_restore_sigint_handler ( ) ;
379+
226380 // Now return the result.
227381 result
228382}
0 commit comments