| 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 | |