@@ -32,17 +32,41 @@ pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitS
32
32
// this seems preferable to not forwarding it in the first place.
33
33
#[ cfg( unix) ]
34
34
let status = {
35
+ use std:: ops:: Deref ;
36
+
35
37
use nix:: sys:: signal;
36
38
use nix:: unistd:: { getpgid, Pid } ;
37
39
use tokio:: select;
38
40
use tokio:: signal:: unix:: { signal as handle_signal, SignalKind } ;
39
41
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
45
66
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
+ }
46
70
47
71
let mut sigterm_handle = handle_signal ( SignalKind :: terminate ( ) ) ?;
48
72
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
56
80
_ = sigint_handle. recv( ) => {
57
81
// See above for commentary on handling of SIGINT.
58
82
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
+
59
89
// Check if the child pgid has changed
60
- let child_pgid = getpgid( child_pid) ?;
90
+ let child_pgid = getpgid( Some ( child_pid) ) ?;
61
91
62
92
// Increment the number of interrupts seen
63
93
sigint_count += 1 ;
@@ -66,15 +96,24 @@ pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitS
66
96
// and we should forward it. If we've received multiple SIGINTs, forward it
67
97
// regardless.
68
98
if child_pgid == parent_pgid && sigint_count < 2 {
99
+ debug!( "Received SIGINT, assuming the child received it as part of the process group" ) ;
69
100
continue ;
70
101
}
71
102
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 ) ;
73
105
} ,
74
106
_ = 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
+
75
113
// We unconditionally forward SIGTERM to the child process; unlike SIGINT, this
76
114
// 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 ) ;
78
117
}
79
118
} ;
80
119
}
@@ -116,30 +155,3 @@ pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitS
116
155
Ok ( ExitStatus :: Failure )
117
156
}
118
157
}
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