use std::time::Duration;
const POLL_INTERVAL: Duration = Duration::from_secs(2);
pub(super) async fn wait_for_parent_death() {
let initial = current_ppid();
watch_ppid_change(initial, current_ppid, POLL_INTERVAL).await;
}
fn current_ppid() -> i32 {
nix::unistd::getppid().as_raw()
}
async fn watch_ppid_change<F>(initial: i32, read_ppid: F, poll: Duration)
where
F: Fn() -> i32,
{
if initial == 1 {
std::future::pending::<()>().await;
return;
}
loop {
tokio::time::sleep(poll).await;
if read_ppid() != initial {
return;
}
}
}
#[cfg(test)]
mod tests {
use super::watch_ppid_change;
use std::sync::Arc;
use std::sync::atomic::{AtomicI32, Ordering};
use std::time::Duration;
#[tokio::test]
async fn resolves_when_parent_pid_changes() {
let ppid = Arc::new(AtomicI32::new(1000));
let reader = {
let p = Arc::clone(&ppid);
move || p.load(Ordering::SeqCst)
};
let flipper = Arc::clone(&ppid);
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(20)).await;
flipper.store(1, Ordering::SeqCst);
});
tokio::time::timeout(
Duration::from_secs(5),
watch_ppid_change(1000, reader, Duration::from_millis(5)),
)
.await
.expect("must resolve promptly once the parent pid changes");
}
#[tokio::test]
async fn does_not_resolve_while_parent_alive() {
let res = tokio::time::timeout(
Duration::from_millis(80),
watch_ppid_change(1000, || 1000_i32, Duration::from_millis(5)),
)
.await;
assert!(res.is_err(), "must not resolve while the parent is alive");
}
#[tokio::test]
async fn ppid_one_disables_detection_and_defers_to_stdin() {
let res = tokio::time::timeout(
Duration::from_millis(80),
watch_ppid_change(1, || 1_i32, Duration::from_millis(5)),
)
.await;
assert!(
res.is_err(),
"ppid == 1 must NOT resolve — defer to stdin EOF, never exit instantly"
);
}
}