33// For the full copyright and license information, please view the LICENSE
44// file that was distributed with this source code.
55
6- // spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld getpid
6+ // spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld getpid kqueue kevent
77mod status;
88
99use crate :: status:: ExitStatus ;
1010use clap:: { Arg , ArgAction , Command } ;
1111use nix:: errno:: Errno ;
1212use nix:: sys:: signal:: { SigSet , SigmaskHow , Signal , sigprocmask} ;
1313use std:: io:: { self , ErrorKind } ;
14+
15+ #[ cfg( target_os = "macos" ) ]
16+ use nix:: sys:: event:: { EventFilter , EventFlag , FilterFlag , KEvent , Kqueue } ;
1417use std:: os:: unix:: process:: { CommandExt , ExitStatusExt } ;
1518use std:: process:: { self , Child , Stdio } ;
1619use std:: sync:: atomic:: { self , AtomicBool } ;
@@ -211,8 +214,11 @@ fn send_signal(process: &mut Child, signal: usize, foreground: bool) {
211214
212215/// Wait for one of the specified signals to be delivered, with optional timeout.
213216///
214- /// This function uses `sigtimedwait()` to efficiently wait for signals without polling.
215- /// It handles EINTR by retrying the wait.
217+ /// This function uses platform-specific mechanisms for efficient signal waiting:
218+ /// - Linux/FreeBSD: `sigtimedwait()` for direct signal waiting
219+ /// - macOS: `kqueue` with EVFILT_SIGNAL for event-driven signal monitoring
220+ ///
221+ /// Both approaches avoid polling and provide sub-millisecond precision.
216222///
217223/// # Arguments
218224/// * `signals` - Signals to wait for (typically SIGCHLD and SIGTERM)
@@ -222,7 +228,9 @@ fn send_signal(process: &mut Child, signal: usize, foreground: bool) {
222228/// * `Ok(Some(signal))` - A signal was received
223229/// * `Ok(None)` - Timeout expired
224230/// * `Err(e)` - An error occurred
231+ #[ cfg( not( target_os = "macos" ) ) ]
225232fn wait_for_signal ( signals : & [ Signal ] , until : Option < Instant > ) -> io:: Result < Option < Signal > > {
233+ // Linux/FreeBSD: Use sigtimedwait() for efficient signal waiting
226234 // Create signal set from the provided signals
227235 let mut sigset = SigSet :: empty ( ) ;
228236 for & sig in signals {
@@ -253,9 +261,9 @@ fn wait_for_signal(signals: &[Signal], until: Option<Instant>) -> io::Result<Opt
253261
254262 let result = unsafe {
255263 libc:: sigtimedwait (
256- sigset. as_ref ( ) as * const libc :: sigset_t ,
264+ std :: ptr :: from_ref ( sigset. as_ref ( ) ) ,
257265 std:: ptr:: null_mut ( ) , // We don't need siginfo
258- & timeout_spec as * const libc :: timespec ,
266+ std :: ptr :: from_ref ( & timeout_spec ) ,
259267 )
260268 } ;
261269
@@ -268,12 +276,74 @@ fn wait_for_signal(signals: &[Signal], until: Option<Instant>) -> io::Result<Opt
268276 // Some other error
269277 err => return Err ( io:: Error :: from ( err) ) ,
270278 }
271- } else {
272- // Signal received - convert signal number to Signal enum
273- return Signal :: try_from ( result)
279+ }
280+ // Signal received - convert signal number to Signal enum
281+ return Signal :: try_from ( result) . map ( Some ) . map_err ( io:: Error :: other) ;
282+ }
283+ }
284+
285+ /// macOS implementation using kqueue with EVFILT_SIGNAL
286+ ///
287+ /// kqueue is the native BSD/macOS mechanism for event notification, providing
288+ /// efficient signal monitoring without polling. This is equivalent in performance
289+ /// to sigtimedwait() on Linux.
290+ #[ cfg( target_os = "macos" ) ]
291+ fn wait_for_signal ( signals : & [ Signal ] , until : Option < Instant > ) -> io:: Result < Option < Signal > > {
292+ // Create a kqueue for signal monitoring
293+ let kq = Kqueue :: new ( ) . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: Other , e. to_string ( ) ) ) ?;
294+
295+ // Create events for each signal we want to monitor
296+ let mut changelist = Vec :: with_capacity ( signals. len ( ) ) ;
297+ for & sig in signals {
298+ let event = KEvent :: new (
299+ sig as usize ,
300+ EventFilter :: EVFILT_SIGNAL ,
301+ EventFlag :: EV_ADD | EventFlag :: EV_ONESHOT ,
302+ FilterFlag :: empty ( ) ,
303+ 0 ,
304+ 0 ,
305+ ) ;
306+ changelist. push ( event) ;
307+ }
308+
309+ // Calculate timeout
310+ let timeout = if let Some ( deadline) = until {
311+ let remaining = deadline. saturating_duration_since ( Instant :: now ( ) ) ;
312+ Some ( libc:: timespec {
313+ tv_sec : remaining. as_secs ( ) as libc:: time_t ,
314+ tv_nsec : remaining. subsec_nanos ( ) as libc:: c_long ,
315+ } )
316+ } else {
317+ None
318+ } ;
319+
320+ // Wait for signal events
321+ let mut eventlist = vec ! [ KEvent :: new(
322+ 0 ,
323+ EventFilter :: EVFILT_SIGNAL ,
324+ EventFlag :: empty( ) ,
325+ FilterFlag :: empty( ) ,
326+ 0 ,
327+ 0 ,
328+ ) ] ;
329+
330+ match kq. kevent ( & changelist, & mut eventlist, timeout) {
331+ Ok ( n) if n > 0 => {
332+ // Signal received - extract signal number from event
333+ let sig_num = eventlist[ 0 ] . ident ( ) as i32 ;
334+ Signal :: try_from ( sig_num)
274335 . map ( Some )
275- . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: Other , e) ) ;
336+ . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: Other , e. to_string ( ) ) )
337+ }
338+ Ok ( _) => {
339+ // Timeout expired with no events
340+ Ok ( None )
341+ }
342+ Err ( Errno :: EINTR ) => {
343+ // Interrupted - retry
344+ wait_for_signal ( signals, until)
276345 }
346+ Err ( e) => Err ( io:: Error :: new ( io:: ErrorKind :: Other , e. to_string ( ) ) ) ,
277347 }
278348}
279349
@@ -443,44 +513,43 @@ fn timeout(
443513 let deadline = Instant :: now ( )
444514 . checked_add ( duration)
445515 . unwrap_or_else ( || Instant :: now ( ) + Duration :: from_secs ( 86400 * 365 * 100 ) ) ;
446- let wait_result: Option < std:: process:: ExitStatus > = loop {
447- // Wait for signals with timeout
448- // If child has already exited, SIGCHLD will be delivered immediately
449- let signal_result = wait_for_signal ( & [ Signal :: SIGCHLD , Signal :: SIGTERM ] , Some ( deadline) ) ;
450- match signal_result {
451- Ok ( Some ( Signal :: SIGCHLD ) ) => {
452- // Child state changed, reap it
453- match process. wait ( ) {
454- Ok ( status) => break Some ( status) ,
455- Err ( e) => {
456- // Restore mask before returning error
457- let _ = sigprocmask ( SigmaskHow :: SIG_SETMASK , Some ( & old_sigset) , None ) ;
458- return Err ( e. into ( ) ) ;
459- }
516+
517+ // Wait for signals with timeout
518+ // If child has already exited, SIGCHLD will be delivered immediately
519+ let signal_result = wait_for_signal ( & [ Signal :: SIGCHLD , Signal :: SIGTERM ] , Some ( deadline) ) ;
520+ let wait_result: Option < std:: process:: ExitStatus > = match signal_result {
521+ Ok ( Some ( Signal :: SIGCHLD ) ) => {
522+ // Child state changed, reap it
523+ match process. wait ( ) {
524+ Ok ( status) => Some ( status) ,
525+ Err ( e) => {
526+ // Restore mask before returning error
527+ let _ = sigprocmask ( SigmaskHow :: SIG_SETMASK , Some ( & old_sigset) , None ) ;
528+ return Err ( e. into ( ) ) ;
460529 }
461530 }
462- Ok ( Some ( Signal :: SIGTERM ) ) => {
463- // External termination request
464- SIGNALED . store ( true , atomic :: Ordering :: Relaxed ) ;
465- break None ; // Treat as timeout
466- }
467- Ok ( None ) => {
468- // Timeout expired
469- break None ;
470- }
471- Ok ( Some ( sig ) ) => {
472- // Unexpected signal (shouldn't happen since we only wait for SIGCHLD/SIGTERM)
473- let _ = sigprocmask ( SigmaskHow :: SIG_SETMASK , Some ( & old_sigset ) , None ) ;
474- return Err ( USimpleError :: new (
475- ExitStatus :: TimeoutFailed . into ( ) ,
476- format ! ( "Unexpected signal received: {:?}" , sig ) ,
477- ) ) ;
478- }
479- Err ( e ) => {
480- // wait_for_signal failed
481- let _ = sigprocmask ( SigmaskHow :: SIG_SETMASK , Some ( & old_sigset ) , None ) ;
482- return Err ( e . into ( ) ) ;
483- }
531+ }
532+ Ok ( Some ( Signal :: SIGTERM ) ) => {
533+ // External termination request
534+ SIGNALED . store ( true , atomic :: Ordering :: Relaxed ) ;
535+ None // Treat as timeout
536+ }
537+ Ok ( None ) => {
538+ // Timeout expired
539+ None
540+ }
541+ Ok ( Some ( sig ) ) => {
542+ // Unexpected signal (shouldn't happen since we only wait for SIGCHLD/SIGTERM)
543+ let _ = sigprocmask ( SigmaskHow :: SIG_SETMASK , Some ( & old_sigset ) , None ) ;
544+ return Err ( USimpleError :: new (
545+ ExitStatus :: TimeoutFailed . into ( ) ,
546+ format ! ( "Unexpected signal received: {sig:?}" ) ,
547+ ) ) ;
548+ }
549+ Err ( e ) => {
550+ // wait_for_signal failed
551+ let _ = sigprocmask ( SigmaskHow :: SIG_SETMASK , Some ( & old_sigset ) , None ) ;
552+ return Err ( e . into ( ) ) ;
484553 }
485554 } ;
486555
0 commit comments