Skip to content

Commit 57b08d3

Browse files
committed
test(supervisor): reproduce fast child reaping race
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent 0f0c94b commit 57b08d3

1 file changed

Lines changed: 43 additions & 0 deletions

File tree

‎crates/openshell-supervisor-process/src/managed_children.rs‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,46 @@ pub fn is_managed(pid: i32) -> bool {
5151
.lock()
5252
.is_ok_and(|children| children.contains(&pid))
5353
}
54+
55+
#[cfg(test)]
56+
mod tests {
57+
use super::{is_managed, register, unregister};
58+
use nix::sys::wait::{Id, WaitPidFlag, WaitStatus, waitid, waitpid};
59+
use nix::unistd::Pid;
60+
use std::process::Command;
61+
62+
#[test]
63+
fn fast_child_remains_waitable_after_orphan_reap_attempt() {
64+
let mut child = Command::new("sh")
65+
.args(["-c", "exit 11"])
66+
.spawn()
67+
.expect("spawn fast child");
68+
let child_pid = child.id();
69+
let pid = Pid::from_raw(i32::try_from(child_pid).unwrap());
70+
71+
// Observe the completed child without consuming its status, exactly
72+
// as the orphan reaper does before its managed-PID check.
73+
loop {
74+
match waitid(
75+
Id::Pid(pid),
76+
WaitPidFlag::WEXITED | WaitPidFlag::WNOHANG | WaitPidFlag::WNOWAIT,
77+
) {
78+
Ok(WaitStatus::StillAlive) => std::thread::yield_now(),
79+
Ok(_) => break,
80+
Err(error) => panic!("observe fast child: {error}"),
81+
}
82+
}
83+
84+
if !is_managed(pid.as_raw()) {
85+
waitpid(pid, Some(WaitPidFlag::WNOHANG)).expect("orphan reaper consumes child");
86+
}
87+
register(child_pid);
88+
89+
let wait = child.wait();
90+
unregister(child_pid);
91+
assert!(
92+
wait.is_ok(),
93+
"the explicit child waiter must retain the exit status"
94+
);
95+
}
96+
}

0 commit comments

Comments
 (0)