#![cfg(all(feature = "process", not(target_os = "wasi")))]
use std::{
pin::pin,
sync::{Arc, Barrier, mpsc},
time::Duration,
};
#[cfg(unix)]
use std::{thread, time::Instant};
use futures::future::poll_immediate;
#[cfg(unix)]
use rustix::{
io::Errno,
process::{Pid, test_kill_process},
};
use zruntime::{
LocalRuntime,
process::{Command, Stdio},
};
#[test]
#[ntest::timeout(30000)]
fn children_are_waited_for_while_blocking_work_fills_the_pool() {
let started = Arc::new(Barrier::new(THREADS + 1));
let release = Arc::new(Barrier::new(THREADS + 1));
for _ in 0..THREADS {
let started = started.clone();
let release = release.clone();
drop(zruntime::unblock(move || {
started.wait();
release.wait();
}));
}
started.wait();
let (report, reported) = mpsc::channel();
drop(zruntime::unblock(move || {
let _ = report.send(());
}));
assert!(
reported.recv_timeout(Duration::from_millis(200)).is_err(),
"the pool has a thread to spare",
);
let runtime = LocalRuntime::new().unwrap();
let mut child = filter().stdin(Stdio::piped()).spawn(&runtime).unwrap();
let input = child.stdin.take();
let (waited, status) = runtime.block_on(async {
let mut status = pin!(child.status());
if let Some(status) = poll_immediate(&mut status).await {
return (false, Ok(status));
}
drop(input);
(true, runtime.timeout(Duration::from_secs(5), status).await)
});
#[cfg(unix)]
let reaped = {
let child = Command::new("sh")
.args(["-c", "sleep 0.3"])
.spawn(&runtime)
.unwrap();
let id = child.id();
drop(child);
disappears(id)
};
release.wait();
assert!(waited, "the child exited before its input ended");
let status = status.expect("the wait for the child never ended").unwrap();
assert!(status.success());
#[cfg(unix)]
assert!(reaped, "the child was never collected");
}
fn filter() -> Command {
Command::new(if cfg!(windows) { "sort" } else { "cat" })
}
#[cfg(unix)]
fn disappears(id: u32) -> bool {
let deadline = Instant::now() + Duration::from_secs(5);
while exists(id) {
if Instant::now() > deadline {
return false;
}
thread::sleep(Duration::from_millis(10));
}
true
}
#[cfg(unix)]
fn exists(id: u32) -> bool {
let pid = Pid::from_raw(id as i32).expect("a child's process ID is not zero");
test_kill_process(pid) != Err(Errno::SRCH)
}
const THREADS: usize = 500;