1 | //! The underlying system reaper. |
2 | //! |
3 | //! There are two backends: |
4 | //! |
5 | //! - signal, which waits for SIGCHLD. |
6 | //! - wait, which waits directly on a process handle. |
7 | //! |
8 | //! "wait" is preferred, but is not available on all supported Linuxes. So we |
9 | //! test to see if pidfd is supported first. If it is, we use wait. If not, we use |
10 | //! signal. |
11 | |
12 | #![allow (irrefutable_let_patterns)] |
13 | |
14 | /// Enable the waiting reaper. |
15 | #[cfg (any(windows, target_os = "linux" ))] |
16 | macro_rules! cfg_wait { |
17 | ($($tt:tt)*) => {$($tt)*}; |
18 | } |
19 | |
20 | /// Enable the waiting reaper. |
21 | #[cfg (not(any(windows, target_os = "linux" )))] |
22 | macro_rules! cfg_wait { |
23 | ($($tt:tt)*) => {}; |
24 | } |
25 | |
26 | /// Enable signals. |
27 | #[cfg (not(windows))] |
28 | macro_rules! cfg_signal { |
29 | ($($tt:tt)*) => {$($tt)*}; |
30 | } |
31 | |
32 | /// Enable signals. |
33 | #[cfg (windows)] |
34 | macro_rules! cfg_signal { |
35 | ($($tt:tt)*) => {}; |
36 | } |
37 | |
38 | cfg_wait! { |
39 | mod wait; |
40 | } |
41 | |
42 | cfg_signal! { |
43 | mod signal; |
44 | } |
45 | |
46 | use std::io; |
47 | use std::sync::Mutex; |
48 | |
49 | /// The underlying system reaper. |
50 | pub(crate) enum Reaper { |
51 | #[cfg (any(windows, target_os = "linux" ))] |
52 | /// The reaper based on the wait backend. |
53 | Wait(wait::Reaper), |
54 | |
55 | /// The reaper based on the signal backend. |
56 | #[cfg (not(windows))] |
57 | Signal(signal::Reaper), |
58 | } |
59 | |
60 | /// The wrapper around a child. |
61 | pub(crate) enum ChildGuard { |
62 | #[cfg (any(windows, target_os = "linux" ))] |
63 | /// The child guard based on the wait backend. |
64 | Wait(wait::ChildGuard), |
65 | |
66 | /// The child guard based on the signal backend. |
67 | #[cfg (not(windows))] |
68 | Signal(signal::ChildGuard), |
69 | } |
70 | |
71 | /// A lock on the reaper. |
72 | pub(crate) enum Lock { |
73 | #[cfg (any(windows, target_os = "linux" ))] |
74 | /// The wait-based reaper needs no lock. |
75 | Wait, |
76 | |
77 | /// The lock for the signal-based reaper. |
78 | #[cfg (not(windows))] |
79 | Signal(signal::Lock), |
80 | } |
81 | |
82 | impl Reaper { |
83 | /// Create a new reaper. |
84 | pub(crate) fn new() -> Self { |
85 | cfg_wait! { |
86 | if wait::available() && !cfg!(async_process_force_signal_backend) { |
87 | return Self::Wait(wait::Reaper::new()); |
88 | } |
89 | } |
90 | |
91 | // Return the signal-based reaper. |
92 | cfg_signal! { |
93 | return Self::Signal(signal::Reaper::new()); |
94 | } |
95 | |
96 | #[allow (unreachable_code)] |
97 | { |
98 | panic!("neither the signal backend nor the waiter backend is available" ) |
99 | } |
100 | } |
101 | |
102 | /// Lock the driver thread. |
103 | /// |
104 | /// This makes it so only one thread can reap at once. |
105 | pub(crate) async fn lock(&'static self) -> Lock { |
106 | cfg_wait! { |
107 | if let Self::Wait(_this) = self { |
108 | // No locking needed. |
109 | return Lock::Wait; |
110 | } |
111 | } |
112 | |
113 | cfg_signal! { |
114 | if let Self::Signal(this) = self { |
115 | // We need to lock. |
116 | return Lock::Signal(this.lock().await); |
117 | } |
118 | } |
119 | |
120 | unreachable!() |
121 | } |
122 | |
123 | /// Reap zombie processes forever. |
124 | pub(crate) async fn reap(&'static self, lock: Lock) -> ! { |
125 | cfg_wait! { |
126 | if let (Self::Wait(this), Lock::Wait) = (self, &lock) { |
127 | this.reap().await; |
128 | } |
129 | } |
130 | |
131 | cfg_signal! { |
132 | if let (Self::Signal(this), Lock::Signal(lock)) = (self, lock) { |
133 | this.reap(lock).await; |
134 | } |
135 | } |
136 | |
137 | unreachable!() |
138 | } |
139 | |
140 | /// Register a child into this reaper. |
141 | pub(crate) fn register(&'static self, child: std::process::Child) -> io::Result<ChildGuard> { |
142 | cfg_wait! { |
143 | if let Self::Wait(this) = self { |
144 | return this.register(child).map(ChildGuard::Wait); |
145 | } |
146 | } |
147 | |
148 | cfg_signal! { |
149 | if let Self::Signal(this) = self { |
150 | return this.register(child).map(ChildGuard::Signal); |
151 | } |
152 | } |
153 | |
154 | unreachable!() |
155 | } |
156 | |
157 | /// Wait for the inner child to complete. |
158 | pub(crate) async fn status( |
159 | &'static self, |
160 | child: &Mutex<crate::ChildGuard>, |
161 | ) -> io::Result<std::process::ExitStatus> { |
162 | cfg_wait! { |
163 | if let Self::Wait(this) = self { |
164 | return this.status(child).await; |
165 | } |
166 | } |
167 | |
168 | cfg_signal! { |
169 | if let Self::Signal(this) = self { |
170 | return this.status(child).await; |
171 | } |
172 | } |
173 | |
174 | unreachable!() |
175 | } |
176 | |
177 | /// Do we have any registered zombie processes? |
178 | pub(crate) fn has_zombies(&'static self) -> bool { |
179 | cfg_wait! { |
180 | if let Self::Wait(this) = self { |
181 | return this.has_zombies(); |
182 | } |
183 | } |
184 | |
185 | cfg_signal! { |
186 | if let Self::Signal(this) = self { |
187 | return this.has_zombies(); |
188 | } |
189 | } |
190 | |
191 | unreachable!() |
192 | } |
193 | } |
194 | |
195 | impl ChildGuard { |
196 | /// Get a reference to the inner process. |
197 | pub(crate) fn get_mut(&mut self) -> &mut std::process::Child { |
198 | cfg_wait! { |
199 | if let Self::Wait(this) = self { |
200 | return this.get_mut(); |
201 | } |
202 | } |
203 | |
204 | cfg_signal! { |
205 | if let Self::Signal(this) = self { |
206 | return this.get_mut(); |
207 | } |
208 | } |
209 | |
210 | unreachable!() |
211 | } |
212 | |
213 | /// Start reaping this child process. |
214 | pub(crate) fn reap(&mut self, reaper: &'static Reaper) { |
215 | cfg_wait! { |
216 | if let (Self::Wait(this), Reaper::Wait(reaper)) = (&mut *self, reaper) { |
217 | this.reap(reaper); |
218 | return; |
219 | } |
220 | } |
221 | |
222 | cfg_signal! { |
223 | if let (Self::Signal(this), Reaper::Signal(reaper)) = (self, reaper) { |
224 | this.reap(reaper); |
225 | return; |
226 | } |
227 | } |
228 | |
229 | unreachable!() |
230 | } |
231 | } |
232 | |