rightkit_child_process/unix_child.rs
1//! The Unix child shared by `OwnedChild` and every `ProcessHandle` clone, and the one place a
2//! signal to a child or its process group is sent.
3//!
4//! # Identity rule
5//!
6//! A pid, and with it the process-group id the child leads, cannot be recycled while the
7//! child is a zombie: the kernel releases the number only when the parent reaps it, and only
8//! this process can reap its own child. So a group signal is safe exactly while this process
9//! still holds the leader as its own **unreaped** child.
10//!
11//! * Every reap goes through [`UnixChild`], under the lock that guards it. The std `Child` is
12//! private, so no path can reap behind this type's back.
13//! * Every group signal is sent under that same lock, after the leader is confirmed unreaped,
14//! and therefore before any reap that could follow it.
15//! * After any reap, by any path (a wait, a `try_wait`, a handle or a clone, the detached
16//! reaper, the public [`OwnedChild::child`](crate::OwnedChild::child) accessor), the
17//! state is `Reaped` for good. Group signalling is over for this child and all its clones:
18//! no signal, and no signal-0 membership probe, is ever sent again.
19//!
20//! * **Unreaped by this type is not unreaped by the kernel.** Other code in the process (a
21//! `waitpid(-1)` loop) can collect our child behind our back, and a replacement child can then
22//! take the same number. So every action that names the pid (a signal, a `waitpid`, a
23//! `waitid` probe, a membership probe) is preceded by [`UnixChild::prove`], which compares the
24//! kernel's description of the pid (parent and start time, read at spawn) with the process
25//! we spawned. A different or missing description marks the leader `Lost` and nothing is
26//! sent or waited. The proof and the call that follows it are two system calls: foreign code
27//! that reaps the child in exactly that gap, and a replacement that takes the number there,
28//! defeat it. This is a known limit; see `identity`.
29//!
30//! # Waiting
31//!
32//! * **A wait on a shared child is a loop of non-blocking looks.** [`wait_shared`] takes the
33//! lock, makes one non-blocking `try_wait` (a `waitpid(WNOHANG)`), releases the lock, sleeps,
34//! and looks again until the leader is reaped or the caller's deadline passes.
35//! [`observe_shared`] does the same with the non-reaping `waitid(WNOHANG | WNOWAIT)` probe.
36//! `ProcessHandle::wait`, `wait_timeout` and `wait_exited*`, `OwnedChild::wait*`,
37//! `wait_or_kill`, `terminate_tree*`, `Drop`, the detached reaper thread
38//! (`rightkit-detached-reap`) and, up to its timeout, the failed-spawn cleanup all wait this
39//! way.
40//! None of them reaps with a blocking call or sleeps while holding the lock, so `kill`,
41//! `terminate_tree` and every other waiter run while one of them is in progress, and a
42//! deadline is honoured whatever the child does.
43//! * The sleep is [`ExitWatch::pause`](crate::exit_watch::ExitWatch::pause): a hint. With an
44//! event source it ends early at the child's exit; without one it is a 5 ms interval. The
45//! event never decides anything, so it cannot make a wait return early or hang.
46//! * **Where the lock is held across a wait or a long action (the complete list).** None of
47//! these is a bounded wait that entered a blocking reap; the first is the only one a
48//! deadline-bearing caller on another thread can be held behind.
49//! 1. [`UnixChild::wait`] through the `OwnedChild::child()` guard. The caller holds the lock
50//! by holding the guard, so the wait cannot release it: a bounded wait on a clone, a
51//! `kill`, `terminate_tree` or `Drop` on another thread waits behind it until the child
52//! has exited, and can exceed its own deadline. That is the caller's choice, not the
53//! crate's: `std::process::Child::wait` through the same guard did exactly this in
54//! 0.3.3 (documented: "release this guard before another accessor or exit reader").
55//! The wait still reaps only with non-blocking looks. A program that needs a deadline
56//! or a cancel to stay live must wait through `OwnedChild`/`ProcessHandle` instead.
57//! 2. `cleanup_direct` with no timeout (`partial_spawn_cleanup_timeout` not set), and
58//! 3. the `rightkit-partial-reap` thread it hands a timed-out child to. Both call
59//! `UnixChild::wait` under the lock, as 0.3.3 called `std::process::Child::wait`. They
60//! run only for a child whose spawn failed after the process existed, and that `Arc`
61//! is held by nothing but the spawning thread (the partial `OwnedChild`, which no
62//! caller has seen and from which no `ProcessHandle` was cloned) and then by the reaper
63//! thread: no other thread can reach the lock, so nothing can be held behind it.
64//! 4. `OwnedChild::while_unreaped` holds the lock while the caller's closure runs, which
65//! is its purpose (no reap can fall between the proof and the action). A bounded wait on
66//! another thread can be delayed by as long as the closure takes.
67//! `rightkit-sandbox` passes a descendant sweep with a 200 ms grace. New since 0.3.3.
68//!
69//! Also, [`UnixChild::require_identity`] reaps a just-killed child with a blocking
70//! `Child::wait`, before the child is shared at all.
71//! * No wait-class system call names the pid while the lock is released. A blocked
72//! `waitpid(pid)`/`waitid(P_PID, pid)` outside the lock could be entered after another reader
73//! reaped the leader and the kernel recycled the number, and would then wait on an unrelated
74//! child.
75//! * Seeing that the leader exited does not require reaping it (`UnixChild::leader_state`), so
76//! an exit monitor can observe the exit and leave the reap, and with it the group's identity,
77//! to the owner.
78//!
79//! The state is one lock-guarded enum, never separate atomics, so no thread can observe a
80//! half-published transition.
81
82use crate::exit_watch::ExitWatch;
83use crate::identity::{Origin, Proof};
84use crate::process_handle::lock_child;
85use std::{
86 io,
87 process::{Child, ChildStderr, ChildStdin, ChildStdout, ExitStatus},
88 sync::{Arc, Mutex},
89 time::Instant,
90};
91
92/// Whether this host can see that the leader exited without reaping it (`probe_exit` is real).
93/// Where it cannot, `ProcessHandle::wait_exited` falls back to a reaping wait.
94pub(crate) const CAN_OBSERVE_WITHOUT_REAPING: bool = cfg!(any(
95 target_os = "linux",
96 target_os = "android",
97 target_os = "macos",
98 target_os = "ios"
99));
100
101/// What this process knows about the leader. Only [`UnixChild`] changes it, under the lock.
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103enum Leader {
104 /// Not reaped by this process: the zombie, or the running process, still pins the id.
105 Unreaped,
106 /// Reaped through this type; the cached status is returned to every later reader.
107 Reaped(ExitStatus),
108 /// The kernel says the pid is no longer our child although we never reaped it (something
109 /// else in the process reaped it). Treated as reaped; the status is lost.
110 Lost,
111}
112
113/// Where the leader is, as far as a non-reaping look can tell.
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub(crate) enum LeaderState {
116 Running,
117 /// Exited but not reaped: a zombie that still pins the id. The last moment a group
118 /// signal is possible.
119 Exited,
120 Reaped,
121}
122
123/// Who is in the leader's process group, besides the leader.
124#[derive(Clone, Debug, PartialEq, Eq)]
125pub(crate) enum Members {
126 Pids(Vec<u32>),
127 /// The host cannot enumerate the group (or it is larger than one listing).
128 Unknown,
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
132pub(crate) enum SignalTarget {
133 /// `kill(-pgid, ..)`: the leader's whole process group.
134 Group,
135 /// `kill(pid, ..)`: the leader alone (detached children, which own only their pid).
136 Leader,
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub(crate) enum SignalOutcome {
141 Sent,
142 /// The leader was already reaped, so nothing was sent.
143 Retired,
144 /// The target was already gone (`ESRCH`, or only zombies were left).
145 Gone,
146 /// A group signal answered `EPERM` while the leader still reads as running (macOS does
147 /// this when the last members are mid-exit). Nothing was sent. The caller may look again
148 /// after the lock is released: the group is gone as soon as the exit completes.
149 MidExit,
150}
151
152/// A spawned Unix child. The same API as Windows' `WindowsChild`: public stdio fields, `id`,
153/// `try_wait`, `wait` and `kill`. It does not expose the std `Child`, because a std reap the
154/// crate cannot see would make later group signals unsafe (see the module documentation).
155///
156/// Obtained through [`OwnedChild::child`](crate::OwnedChild::child), which holds the shared
157/// lock while the guard lives. `wait` through the guard therefore holds that lock until the
158/// child has exited, and every other thread's access to the same child (a bounded wait on a
159/// clone, `kill`, `terminate_tree`, `Drop`) waits behind it, as `std::process::Child::wait`
160/// through the guard did in 0.3.3. Prefer `OwnedChild::wait`, `wait_timeout` or
161/// `ProcessHandle::wait`, which release the lock between looks.
162#[derive(Debug)]
163pub struct UnixChild {
164 pub stdin: Option<ChildStdin>,
165 pub stdout: Option<ChildStdout>,
166 pub stderr: Option<ChildStderr>,
167 child: Child,
168 pid: u32,
169 leader: Leader,
170 /// Who the leader was at spawn. Checked by [`UnixChild::prove`] before any action by pid.
171 origin: Origin,
172 /// The wake-up hint for waiters (never a verdict), opened at spawn and shared with every
173 /// waiter.
174 watch: Arc<ExitWatch>,
175 /// Test only: the id used as the signal target, to stand in for a recycled id.
176 #[cfg(test)]
177 recycled_id: Option<u32>,
178}
179
180impl UnixChild {
181 pub(crate) fn new(mut child: Child) -> Self {
182 // Both are taken now, while the child is certainly still ours: the hint is bound to the
183 // process, and the identity names it. (A child collected by foreign code before this
184 // point would hand its replacement's description over as the baseline; that is part of
185 // the known limit described in `identity`.)
186 let watch = Arc::new(ExitWatch::open(child.id()));
187 let origin = Origin::capture(child.id());
188 Self {
189 stdin: child.stdin.take(),
190 stdout: child.stdout.take(),
191 stderr: child.stderr.take(),
192 pid: child.id(),
193 origin,
194 watch,
195 child,
196 leader: Leader::Unreaped,
197 #[cfg(test)]
198 recycled_id: None,
199 }
200 }
201
202 /// The wake-up hint, for waiters that sleep without holding the lock.
203 pub(crate) fn exit_watch(&self) -> Arc<ExitWatch> {
204 Arc::clone(&self.watch)
205 }
206
207 pub fn id(&self) -> u32 {
208 self.pid
209 }
210
211 /// Spawn-time check: a host that has an identity source but would not describe the child
212 /// cannot prove later that its pid is still the child's, so the child is not handed out.
213 /// It is killed and reaped here, where the pid is certainly still its own.
214 pub(crate) fn require_identity(&mut self) -> io::Result<()> {
215 if !matches!(self.origin, Origin::Missing) {
216 return Ok(());
217 }
218 let _ = self.child.kill();
219 let _ = self.child.wait();
220 self.leader = Leader::Lost;
221 Err(io::Error::other(
222 "the spawned child could not be identified; it was stopped because a later \
223 signal could not be proven to reach it",
224 ))
225 }
226
227 /// The one proof every action by pid starts with: `Ok(true)` when the pid still names the
228 /// process this type spawned and has not reaped. `Ok(false)` (and the leader becomes
229 /// `Lost`, never touched again) when the number changed hands or the process is gone.
230 /// `Err` when the kernel cannot be asked: the caller must then act on nothing.
231 fn prove(&mut self) -> io::Result<bool> {
232 if !matches!(self.leader, Leader::Unreaped) {
233 return Ok(false);
234 }
235 #[cfg(test)]
236 trace::record(trace::Event::Proof { leader: self.pid });
237 if self.origin.check(self.pid)? == Proof::Same {
238 Ok(true)
239 } else {
240 self.leader = Leader::Lost;
241 Ok(false)
242 }
243 }
244
245 /// `prove`, as the guard of a wait: an unproven pid is reported as `ECHILD`, as the kernel
246 /// would for a child that is not ours.
247 fn require_ours(&mut self) -> io::Result<()> {
248 if self.prove()? {
249 Ok(())
250 } else {
251 Err(io::Error::from_raw_os_error(libc::ECHILD))
252 }
253 }
254
255 /// Test only: pretend the pid was described differently at spawn, as if our child had been
256 /// collected elsewhere and the number given to the process now holding it.
257 #[cfg(test)]
258 pub(crate) fn simulate_replaced(&mut self) {
259 self.origin = Origin::Known(crate::identity::Identity {
260 parent: 0,
261 started_sec: 1,
262 started_usec: 1,
263 });
264 }
265
266 /// Non-blocking. A `Some` result means the leader has been reaped, and ends group
267 /// signalling for this child.
268 pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
269 match self.leader {
270 Leader::Reaped(status) => return Ok(Some(status)),
271 Leader::Lost => return Err(io::Error::from_raw_os_error(libc::ECHILD)),
272 Leader::Unreaped => {}
273 }
274 if let Err(error) = self.require_ours() {
275 return Err(self.note_error(error));
276 }
277 #[cfg(test)]
278 trace::record(trace::Event::Wait { leader: self.pid });
279 match self.child.try_wait() {
280 Ok(Some(status)) => {
281 self.note_reaped(status);
282 Ok(Some(status))
283 }
284 Ok(None) => Ok(None),
285 Err(error) => Err(self.note_error(error)),
286 }
287 }
288
289 /// Closes stdin as std does, then waits until the leader is reaped. The reap itself is only
290 /// ever a non-blocking `waitpid(WNOHANG)` between sleeps, but this method cannot release
291 /// the lock: when it is reached through the `OwnedChild::child()` guard the caller holds
292 /// it for the whole wait, so other threads' waits, kills and drops on the same child
293 /// queue behind it (as with `std::process::Child::wait` in 0.3.3). Use `OwnedChild::wait`
294 /// or `ProcessHandle::wait` where that matters.
295 pub fn wait(&mut self) -> io::Result<ExitStatus> {
296 drop(self.stdin.take());
297 self.wait_reaping()
298 }
299
300 /// `SIGKILL` to the leader alone, only while it is unreaped. Nothing is sent, and `Ok` is
301 /// returned, once it has been reaped.
302 pub fn kill(&mut self) -> io::Result<()> {
303 self.signal(SignalTarget::Leader, libc::SIGKILL).map(|_| ())
304 }
305
306 /// Reaps without touching stdin, for the guard's `wait` and for cleanup of a child that
307 /// has just been killed. Each look is proven and non-blocking; between looks the thread
308 /// sleeps on the hint. A blocking `waitpid(pid)` from the start would name the number for
309 /// as long as it sleeps, and if other code collected the leader meanwhile it would wait on
310 /// whatever child the kernel gave the number to, and take its status.
311 pub(crate) fn wait_reaping(&mut self) -> io::Result<ExitStatus> {
312 let watch = Arc::clone(&self.watch);
313 watch
314 .wait_for(None, || self.try_wait())?
315 .ok_or_else(|| io::Error::other("an unbounded wait ended without a status"))
316 }
317
318 /// Looks at the leader without reaping it. `Lost` is recorded here when the kernel no
319 /// longer lists the pid as our child.
320 pub(crate) fn leader_state(&mut self) -> io::Result<LeaderState> {
321 if !self.prove()? {
322 return Ok(LeaderState::Reaped);
323 }
324 match probe_exit(self.pid) {
325 // The probe names the number once more; a replacement that took it in between and
326 // has itself exited would read as our zombie. A zombie is describable, so ask again.
327 Ok(true) => Ok(if self.prove()? {
328 LeaderState::Exited
329 } else {
330 LeaderState::Reaped
331 }),
332 Ok(false) => Ok(LeaderState::Running),
333 Err(error) if error.raw_os_error() == Some(libc::ECHILD) => {
334 self.leader = Leader::Lost;
335 Ok(LeaderState::Reaped)
336 }
337 Err(error) => Err(error),
338 }
339 }
340
341 /// Sends `signal` only if the leader is still unreaped, and does so under the caller's
342 /// lock, so no reap can fall between the check and the signal.
343 pub(crate) fn signal(
344 &mut self,
345 target: SignalTarget,
346 signal: i32,
347 ) -> io::Result<SignalOutcome> {
348 if self.leader_state()? == LeaderState::Reaped {
349 return Ok(SignalOutcome::Retired);
350 }
351 // The state above looked at the pid; prove it once more immediately before it is used
352 // as a signal target.
353 if !self.prove()? {
354 return Ok(SignalOutcome::Retired);
355 }
356 let id = self.signal_id() as i32;
357 let target_id = match target {
358 SignalTarget::Group => -id,
359 SignalTarget::Leader => id,
360 };
361 if raw_kill(self.pid, target_id, signal) == 0 {
362 return Ok(SignalOutcome::Sent);
363 }
364 let error = io::Error::last_os_error();
365 match error.raw_os_error() {
366 Some(libc::ESRCH) => Ok(SignalOutcome::Gone),
367 // macOS answers a group signal with EPERM, not ESRCH, when no member can take it:
368 // only zombies are left, or the last members are mid-exit. Our own children are
369 // always signalable, so this is never a real denial. Once the leader is no longer
370 // running (exited, or reaped by a handle clone) the group is gone. A leader that
371 // still reads as running is mid-exit: say so, and let the caller look again once it
372 // has released the lock (`OwnedChild::signal`).
373 Some(libc::EPERM) if target == SignalTarget::Group => {
374 Ok(if self.leader_state()? == LeaderState::Running {
375 SignalOutcome::MidExit
376 } else {
377 SignalOutcome::Gone
378 })
379 }
380 _ => Err(error),
381 }
382 }
383
384 /// The processes other than the leader still in the group.
385 /// Valid only while the leader is unreaped; a reaped leader has no group to speak of. An
386 /// unknown answer is [`Members::Unknown`]: the caller then waits out its grace instead of
387 /// cutting it short.
388 pub(crate) fn group_other_members(&mut self) -> io::Result<Members> {
389 if self.leader_state()? == LeaderState::Reaped {
390 return Ok(Members::Pids(Vec::new()));
391 }
392 group_members_besides(self.pid, self.signal_id())
393 }
394
395 /// True when a process other than the leader is still in the group (unknown counts as yes).
396 #[cfg(test)]
397 pub(crate) fn group_has_other_members(&mut self) -> io::Result<bool> {
398 Ok(match self.group_other_members()? {
399 Members::Pids(pids) => !pids.is_empty(),
400 Members::Unknown => true,
401 })
402 }
403
404 fn signal_id(&self) -> u32 {
405 #[cfg(test)]
406 if let Some(id) = self.recycled_id {
407 return id;
408 }
409 self.pid
410 }
411
412 /// Test only: aim every later signal at `id`, as if the kernel had recycled the leader's
413 /// pid and group id for an unrelated group.
414 #[cfg(test)]
415 pub(crate) fn simulate_recycled_id(&mut self, id: u32) {
416 self.recycled_id = Some(id);
417 }
418
419 fn note_reaped(&mut self, status: ExitStatus) {
420 self.leader = Leader::Reaped(status);
421 #[cfg(test)]
422 trace::record(trace::Event::Reaped { leader: self.pid });
423 }
424
425 fn note_error(&mut self, error: io::Error) -> io::Error {
426 if error.raw_os_error() == Some(libc::ECHILD) {
427 self.leader = Leader::Lost;
428 }
429 error
430 }
431}
432
433/// `waitid(P_PID, pid, WEXITED | WNOHANG | WNOWAIT)`: has the child exited, without reaping
434/// it? `ECHILD` means the pid is not (or no longer) our child.
435#[cfg(any(
436 target_os = "linux",
437 target_os = "android",
438 target_os = "macos",
439 target_os = "ios"
440))]
441fn probe_exit(pid: u32) -> io::Result<bool> {
442 loop {
443 #[cfg(test)]
444 trace::record(trace::Event::Wait { leader: pid });
445 // SAFETY: an all-zero siginfo_t is valid, and waitid fills it.
446 let mut info: libc::siginfo_t = unsafe { std::mem::zeroed() };
447 // SAFETY: valid out pointer; WNOWAIT leaves the zombie in place.
448 let result = unsafe {
449 libc::waitid(
450 libc::P_PID,
451 pid as libc::id_t,
452 &mut info,
453 libc::WEXITED | libc::WNOHANG | libc::WNOWAIT,
454 )
455 };
456 if result == 0 {
457 #[cfg(any(target_os = "linux", target_os = "android"))]
458 // SAFETY: si_pid is valid after a successful waitid.
459 let reported = unsafe { info.si_pid() };
460 #[cfg(not(any(target_os = "linux", target_os = "android")))]
461 let reported = info.si_pid;
462 return Ok(reported as u32 == pid);
463 }
464 let error = io::Error::last_os_error();
465 if error.raw_os_error() != Some(libc::EINTR) {
466 return Err(error);
467 }
468 }
469}
470
471/// Other Unix hosts have no verified non-reaping probe here. The reaped state still guards
472/// every signal; only the early "leader exited" observation is unavailable.
473#[cfg(not(any(
474 target_os = "linux",
475 target_os = "android",
476 target_os = "macos",
477 target_os = "ios"
478)))]
479fn probe_exit(_pid: u32) -> io::Result<bool> {
480 Ok(false)
481}
482
483/// The single place a signal reaches a child or its group, so tests can observe every one
484/// (signal 0 included).
485fn raw_kill(leader: u32, target: i32, signal: i32) -> i32 {
486 #[cfg(test)]
487 trace::record(trace::Event::Signal {
488 leader,
489 target,
490 signal,
491 });
492 #[cfg(not(test))]
493 let _ = leader;
494 // SAFETY: plain kill(2). The callers hold the lock and have just confirmed the leader is
495 // unreaped, which pins the target id.
496 unsafe { libc::kill(target, signal) }
497}
498
499#[cfg(target_os = "macos")]
500fn group_members_besides(leader: u32, group: u32) -> io::Result<Members> {
501 /// `PROC_PGRP_ONLY` from `<libproc.h>`: list the members of one process group.
502 const PROC_PGRP_ONLY: u32 = 2;
503 const CAPACITY: usize = 256;
504 let mut pids = [0 as libc::pid_t; CAPACITY];
505 // SAFETY: the buffer is CAPACITY pids and the size passed says so.
506 let bytes = unsafe {
507 libc::proc_listpids(
508 PROC_PGRP_ONLY,
509 group,
510 pids.as_mut_ptr().cast(),
511 std::mem::size_of_val(&pids) as libc::c_int,
512 )
513 };
514 if bytes <= 0 {
515 // Fall back to signal 0: ESRCH, or EPERM when only zombies remain, means empty.
516 if raw_kill(leader, -(group as i32), 0) == 0 {
517 return Ok(Members::Unknown);
518 }
519 return Ok(match io::Error::last_os_error().raw_os_error() {
520 Some(libc::ESRCH) | Some(libc::EPERM) => Members::Pids(Vec::new()),
521 _ => Members::Unknown,
522 });
523 }
524 let count = (bytes as usize / std::mem::size_of::<libc::pid_t>()).min(CAPACITY);
525 if count >= CAPACITY {
526 return Ok(Members::Unknown);
527 }
528 Ok(Members::Pids(
529 pids[..count]
530 .iter()
531 .filter(|&&pid| pid > 0 && pid as u32 != leader)
532 .map(|&pid| pid as u32)
533 .collect(),
534 ))
535}
536
537#[cfg(any(target_os = "linux", target_os = "android"))]
538fn group_members_besides(leader: u32, group: u32) -> io::Result<Members> {
539 // kill(-pgid, 0) cannot tell an exited, unreaped leader from a live member, so read
540 // /proc: a member is any non-zombie process whose group is this one.
541 let Ok(entries) = std::fs::read_dir("/proc") else {
542 return Ok(Members::Unknown);
543 };
544 let mut members = Vec::new();
545 for entry in entries.flatten() {
546 let Some(pid) = entry
547 .file_name()
548 .to_str()
549 .and_then(|name| name.parse::<u32>().ok())
550 else {
551 continue;
552 };
553 if pid == leader {
554 continue;
555 }
556 let Ok(stat) = std::fs::read_to_string(format!("/proc/{pid}/stat")) else {
557 continue; // exited while we looked
558 };
559 let Some(rest) = stat.rsplit_once(')').map(|(_, rest)| rest) else {
560 return Ok(Members::Unknown);
561 };
562 let mut fields = rest.split_whitespace();
563 let (Some(state), Some(_parent), Some(pgrp)) =
564 (fields.next(), fields.next(), fields.next())
565 else {
566 return Ok(Members::Unknown);
567 };
568 if pgrp.parse::<u32>().ok() == Some(group) && state != "Z" && state != "X" {
569 members.push(pid);
570 }
571 }
572 Ok(Members::Pids(members))
573}
574
575#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
576fn group_members_besides(_leader: u32, _group: u32) -> io::Result<Members> {
577 Ok(Members::Unknown)
578}
579
580/// Waits for the leader of `shared` to be reaped, or for `deadline` to pass (`Ok(None)`); `None`
581/// waits without limit.
582///
583/// Every look is one non-blocking `try_wait` under the lock, released before the thread sleeps
584/// (see the module documentation), so a deadline is honoured whatever the child does, a
585/// `kill` or `terminate_tree` from another thread runs at once, and no wait-class system call
586/// names the pid while the lock is free. `ProcessHandle` waits this way, and so does the
587/// failed-spawn cleanup that has a timeout; the cleanup without one waits through
588/// `UnixChild::wait` instead (see the module documentation, item 2).
589pub(crate) fn wait_shared(
590 shared: &Mutex<UnixChild>,
591 deadline: Option<Instant>,
592) -> io::Result<Option<ExitStatus>> {
593 let (watch, pid) = {
594 let child = lock_child(shared);
595 (child.exit_watch(), child.id())
596 };
597 let mut first = true;
598 watch.wait_for(deadline, || {
599 let looked = lock_child(shared).try_wait();
600 pause_for_test(&mut first, pid, matches!(looked, Ok(None)));
601 looked
602 })
603}
604
605/// Like [`wait_shared`] but does not reap: `Ok(true)` once the leader has exited (or was reaped
606/// by someone), `Ok(false)` when `deadline` passed first. The zombie keeps pinning the group id.
607pub(crate) fn observe_shared(
608 shared: &Mutex<UnixChild>,
609 deadline: Option<Instant>,
610) -> io::Result<bool> {
611 let (watch, pid) = {
612 let child = lock_child(shared);
613 (child.exit_watch(), child.id())
614 };
615 let mut first = true;
616 let seen = watch.wait_for(deadline, || {
617 let state = lock_child(shared).leader_state();
618 pause_for_test(&mut first, pid, matches!(state, Ok(LeaderState::Running)));
619 Ok(match state? {
620 LeaderState::Running => None,
621 LeaderState::Exited | LeaderState::Reaped => Some(()),
622 })
623 })?;
624 Ok(seen.is_some())
625}
626
627/// Test seam: after the first look that found the child still running, with the lock already
628/// released and before the thread sleeps, run the hook installed for `pid` (see [`gate`]).
629#[cfg(test)]
630fn pause_for_test(first: &mut bool, pid: u32, running: bool) {
631 if *first && running {
632 *first = false;
633 gate::pause(pid);
634 }
635}
636
637#[cfg(not(test))]
638fn pause_for_test(_first: &mut bool, _pid: u32, _running: bool) {}
639
640/// Test instrumentation: every signal, every reap and every wait-class system call that names
641/// the leader's pid, in the order they happened. All are recorded under the child's lock, so the
642/// order is the real order.
643#[cfg(test)]
644pub(crate) mod trace {
645 use std::sync::Mutex;
646
647 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
648 pub(crate) enum Event {
649 Signal {
650 leader: u32,
651 target: i32,
652 signal: i32,
653 },
654 Reaped {
655 leader: u32,
656 },
657 /// The pid was proven to still name the spawned process (see `UnixChild::prove`).
658 Proof {
659 leader: u32,
660 },
661 /// A `waitpid`/`waitid` (probe or reap) was issued naming this pid. After `Reaped` the
662 /// number may belong to someone else, so none may follow.
663 Wait {
664 leader: u32,
665 },
666 }
667
668 static EVENTS: Mutex<Vec<Event>> = Mutex::new(Vec::new());
669
670 pub(crate) fn record(event: Event) {
671 EVENTS.lock().unwrap_or_else(|p| p.into_inner()).push(event);
672 }
673
674 pub(crate) fn mark() -> usize {
675 EVENTS.lock().unwrap_or_else(|p| p.into_inner()).len()
676 }
677
678 /// Signals and reaps about `leader` recorded since `mark` (wait calls are left out; see
679 /// [`since_with_waits`]).
680 pub(crate) fn since(mark: usize, leader: u32) -> Vec<Event> {
681 since_with_waits(mark, leader)
682 .into_iter()
683 .filter(|event| !matches!(event, Event::Wait { .. } | Event::Proof { .. }))
684 .collect()
685 }
686
687 /// Every event about `leader` recorded since `mark`, wait calls included.
688 pub(crate) fn since_with_waits(mark: usize, leader: u32) -> Vec<Event> {
689 EVENTS.lock().unwrap_or_else(|p| p.into_inner())[mark..]
690 .iter()
691 .copied()
692 .filter(|event| match event {
693 Event::Signal { leader: l, .. }
694 | Event::Reaped { leader: l }
695 | Event::Proof { leader: l }
696 | Event::Wait { leader: l } => *l == leader,
697 })
698 .collect()
699 }
700}
701
702/// Test seam for waiters: a pause point a test can use to hold a waiter at the exact moment
703/// the old blocking wait had checked the shared state and was about to name the pid.
704#[cfg(test)]
705pub(crate) mod gate {
706 use std::sync::{Arc, Mutex};
707
708 type Hook = Arc<dyn Fn() + Send + Sync>;
709
710 static HOOKS: Mutex<Vec<(u32, Hook)>> = Mutex::new(Vec::new());
711
712 /// Runs `hook` each time a waiter on `pid` reaches its pause point.
713 pub(crate) fn install(pid: u32, hook: impl Fn() + Send + Sync + 'static) {
714 let mut hooks = HOOKS.lock().unwrap_or_else(|p| p.into_inner());
715 hooks.retain(|(id, _)| *id != pid);
716 hooks.push((pid, Arc::new(hook)));
717 }
718
719 pub(crate) fn remove(pid: u32) {
720 HOOKS
721 .lock()
722 .unwrap_or_else(|p| p.into_inner())
723 .retain(|(id, _)| *id != pid);
724 }
725
726 /// Called by a waiter after it released the child lock and before it sleeps.
727 pub(crate) fn pause(pid: u32) {
728 let hook = HOOKS
729 .lock()
730 .unwrap_or_else(|p| p.into_inner())
731 .iter()
732 .find(|(id, _)| *id == pid)
733 .map(|(_, hook)| Arc::clone(hook));
734 if let Some(hook) = hook {
735 hook();
736 }
737 }
738}