@@ -366,29 +366,10 @@ fn resize_terminal() {
366366#[ expect( clippy:: print_stdout, reason = "subprocess test output" ) ]
367367fn send_ctrl_c_interrupts_process ( ) {
368368 let cmd = CommandBuilder :: from ( command_for_fn ! ( ( ) , |( ) : ( ) | {
369- use std:: io:: { Write , stdin, stdout} ;
370- #[ cfg( unix) ]
371- use std:: sync:: Arc ;
372- #[ cfg( unix) ]
373- use std:: sync:: atomic:: { AtomicBool , Ordering } ;
374-
375- #[ cfg( unix) ]
376- let interrupted = Arc :: new( AtomicBool :: new( false ) ) ;
377- #[ cfg( unix) ]
378- let interrupted_clone = Arc :: clone( & interrupted) ;
369+ use std:: io:: { Write , stdout} ;
379370
380- // Install SIGINT handler on Unix
381- #[ cfg( unix) ]
382- // SAFETY: The closure only performs an atomic store, which is signal-safe.
383- unsafe {
384- signal_hook:: low_level:: register( signal_hook:: consts:: SIGINT , move || {
385- interrupted_clone. store( true , Ordering :: SeqCst ) ;
386- } )
387- . unwrap( ) ;
388- }
389-
390- // On Windows, explicitly ensure Ctrl+C is NOT ignored, so that
391- // CTRL_C_EVENT terminates the process via the default handler.
371+ // On Windows, clear the "ignore CTRL_C" flag set by Rust runtime
372+ // so that CTRL_C_EVENT reaches the ctrlc handler.
392373 #[ cfg( windows) ]
393374 {
394375 // SAFETY: Declaring correct signature for SetConsoleCtrlHandler from kernel32.
@@ -399,29 +380,28 @@ fn send_ctrl_c_interrupts_process() {
399380 ) -> i32 ;
400381 }
401382
402- // SAFETY: Clearing the "ignore CTRL_C" flag so the default handler runs .
383+ // SAFETY: Clearing the "ignore CTRL_C" flag so handlers are invoked .
403384 unsafe {
404385 SetConsoleCtrlHandler ( None , 0 ) ; // FALSE = remove ignore
405386 }
406387 }
407388
389+ ctrlc:: set_handler( move || {
390+ // Write directly and exit from the handler to avoid races.
391+ use std:: io:: Write ;
392+ let _ = write!( std:: io:: stdout( ) , "INTERRUPTED" ) ;
393+ let _ = std:: io:: stdout( ) . flush( ) ;
394+ std:: process:: exit( 0 ) ;
395+ } )
396+ . unwrap( ) ;
397+
408398 println!( "ready" ) ;
409399 stdout( ) . flush( ) . unwrap( ) ;
410400
411- // Block on stdin. On Unix, SIGINT interrupts read_line. On Windows,
412- // CTRL_C_EVENT terminates the process via the default handler.
413- let mut input = std:: string:: String :: new( ) ;
414- let _ = stdin( ) . read_line( & mut input) ;
415-
416- // On Unix, check if SIGINT was delivered via the signal handler.
417- // On Windows, this code is unreachable: the process is terminated
418- // by CTRL_C_EVENT before read_line returns.
419- #[ cfg( unix) ]
420- if interrupted. load( Ordering :: SeqCst ) {
421- println!( "INTERRUPTED" ) ;
401+ // Block until Ctrl+C handler exits the process.
402+ loop {
403+ std:: thread:: park( ) ;
422404 }
423-
424- stdout( ) . flush( ) . unwrap( ) ;
425405 } ) ) ;
426406
427407 let mut terminal = Terminal :: spawn ( ScreenSize { rows : 80 , cols : 80 } , cmd) . unwrap ( ) ;
@@ -432,23 +412,10 @@ fn send_ctrl_c_interrupts_process() {
432412 // Send Ctrl+C
433413 terminal. send_ctrl_c ( ) . unwrap ( ) ;
434414
435- // On Unix, send newline to unblock read_line and verify SIGINT detection.
436- #[ cfg( unix) ]
437- {
438- terminal. write ( b"\n " ) . unwrap ( ) ;
439- terminal. read_until ( "INTERRUPTED" ) . unwrap ( ) ;
440- }
415+ // Verify interruption was detected
416+ terminal. read_until ( "INTERRUPTED" ) . unwrap ( ) ;
441417
442- let status = terminal. read_to_end ( ) . unwrap ( ) ;
443-
444- // On Unix, the process exits normally after detecting SIGINT.
445- #[ cfg( unix) ]
446- assert ! ( status. success( ) ) ;
447-
448- // On Windows, the default CTRL_C handler calls ExitProcess with
449- // STATUS_CONTROL_C_EXIT, resulting in a non-zero exit code.
450- #[ cfg( windows) ]
451- assert ! ( !status. success( ) ) ;
418+ let _ = terminal. read_to_end ( ) . unwrap ( ) ;
452419}
453420
454421#[ test]
0 commit comments