@@ -32,17 +32,41 @@ pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitS
3232 // this seems preferable to not forwarding it in the first place.
3333 #[ cfg( unix) ]
3434 let status = {
35+ use std:: ops:: Deref ;
36+
3537 use nix:: sys:: signal;
3638 use nix:: unistd:: { getpgid, Pid } ;
3739 use tokio:: select;
3840 use tokio:: signal:: unix:: { signal as handle_signal, SignalKind } ;
3941
40- // Get the parent and child process group ids
41- let child_pid = handle
42- . id ( )
43- . and_then ( |id| id. try_into ( ) . ok ( ) )
44- . map ( Pid :: from_raw) ;
42+ /// Simple new type for `Pid` allowing construction from [`Child`].
43+ ///
44+ /// `None` if the child process has exited or the PID is invalid.
45+ struct ChildPid ( Option < Pid > ) ;
46+
47+ impl Deref for ChildPid {
48+ type Target = Option < Pid > ;
49+ fn deref ( & self ) -> & Self :: Target {
50+ & self . 0
51+ }
52+ }
53+
54+ impl From < & Child > for ChildPid {
55+ fn from ( child : & Child ) -> Self {
56+ Self (
57+ child
58+ . id ( )
59+ . and_then ( |id| id. try_into ( ) . ok ( ) )
60+ . map ( Pid :: from_raw) ,
61+ )
62+ }
63+ }
64+
65+ // Get the parent PGID
4566 let parent_pgid = getpgid ( None ) ?;
67+ if let Some ( child_pid) = * ChildPid :: from ( & handle) {
68+ debug ! ( "Spawned child {child_pid} in process group {parent_pgid}" ) ;
69+ }
4670
4771 let mut sigterm_handle = handle_signal ( SignalKind :: terminate ( ) ) ?;
4872 let mut sigint_handle = handle_signal ( SignalKind :: interrupt ( ) ) ?;
@@ -56,8 +80,14 @@ pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitS
5680 _ = sigint_handle. recv( ) => {
5781 // See above for commentary on handling of SIGINT.
5882
83+ // If the child has already exited, we can't send it signals
84+ let Some ( child_pid) = * ChildPid :: from( & handle) else {
85+ debug!( "Received SIGINT, but the child has already exited" ) ;
86+ continue ;
87+ } ;
88+
5989 // Check if the child pgid has changed
60- let child_pgid = getpgid( child_pid) ?;
90+ let child_pgid = getpgid( Some ( child_pid) ) ?;
6191
6292 // Increment the number of interrupts seen
6393 sigint_count += 1 ;
@@ -66,15 +96,24 @@ pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitS
6696 // and we should forward it. If we've received multiple SIGINTs, forward it
6797 // regardless.
6898 if child_pgid == parent_pgid && sigint_count < 2 {
99+ debug!( "Received SIGINT, assuming the child received it as part of the process group" ) ;
69100 continue ;
70101 }
71102
72- let _ = send_signal( & handle, child_pid, signal:: Signal :: SIGINT ) ;
103+ debug!( "Received SIGINT, forwarding to child at {child_pid}" ) ;
104+ let _ = signal:: kill( child_pid, signal:: Signal :: SIGINT ) ;
73105 } ,
74106 _ = sigterm_handle. recv( ) => {
107+ // If the child has already exited, we can't send it signals
108+ let Some ( child_pid) = * ChildPid :: from( & handle) else {
109+ debug!( "Received SIGINT, but the child has already exited" ) ;
110+ continue ;
111+ } ;
112+
75113 // We unconditionally forward SIGTERM to the child process; unlike SIGINT, this
76114 // isn't usually handled by the shell and in cases like
77- let _ = send_signal( & handle, child_pid, signal:: Signal :: SIGTERM ) ;
115+ debug!( "Received SIGTERM, forwarding to child at {child_pid}" ) ;
116+ let _ = signal:: kill( child_pid, signal:: Signal :: SIGTERM ) ;
78117 }
79118 } ;
80119 }
@@ -116,30 +155,3 @@ pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitS
116155 Ok ( ExitStatus :: Failure )
117156 }
118157}
119-
120- /// Send a signal to a child process on Unix.
121- ///
122- /// Includes a safety check that the process has not exited.
123- #[ cfg( unix) ]
124- fn send_signal (
125- child : & Child ,
126- child_pid : Option < nix:: unistd:: Pid > ,
127- signal : nix:: sys:: signal:: Signal ,
128- ) -> anyhow:: Result < ( ) > {
129- use nix:: sys:: signal;
130-
131- // If the child has already exited, we can't send it signals
132- let Some ( child_pid) = child_pid else {
133- anyhow:: bail!( "Child process has already exited" ) ;
134- } ;
135-
136- // The child can exit and a different process can take its PID; this may be
137- // overly defensive but seems better than sending a signal to the wrong process.
138- if child. id ( ) . is_none ( ) {
139- anyhow:: bail!( "Child process has already exited" ) ;
140- }
141-
142- signal:: kill ( child_pid, signal) ?;
143-
144- Ok ( ( ) )
145- }
0 commit comments