#[cfg(unix)]
mod reaper {
use std::io;
use std::process::Child;
use std::sync::OnceLock;
use std::sync::mpsc::{self, Receiver, Sender};
use std::time::Duration;
const POLL_INTERVAL: Duration = Duration::from_millis(10);
static SENDER: OnceLock<Result<Sender<Child>, String>> = OnceLock::new();
fn sender() -> io::Result<&'static Sender<Child>> {
match SENDER.get_or_init(|| {
let (sender, receiver) = mpsc::channel();
std::thread::Builder::new()
.name("processkit-detached-reaper".into())
.spawn(move || reap_loop(receiver))
.map(|_| sender)
.map_err(|source| format!("could not start detached reaper: {source}"))
}) {
Ok(sender) => Ok(sender),
Err(message) => Err(io::Error::other(message.clone())),
}
}
pub(super) fn prepare() -> io::Result<()> {
sender().map(|_| ())
}
fn wait_until_reaped(mut child: Child) -> Option<io::Error> {
let mut first_error = None;
loop {
match child.wait() {
Ok(_) => return first_error,
Err(source) => {
first_error.get_or_insert(source);
std::thread::sleep(POLL_INTERVAL);
}
}
}
}
fn finish_failed_handoff(child: Child, handoff_error: io::Error) -> io::Error {
match wait_until_reaped(child) {
None => handoff_error,
Some(wait_error) => io::Error::other(format!(
"{handoff_error}; fallback wait initially failed: {wait_error}"
)),
}
}
pub(super) fn handoff(child: Child) -> io::Result<()> {
let sender = match sender() {
Ok(sender) => sender,
Err(source) => {
return Err(finish_failed_handoff(child, source));
}
};
match sender.send(child) {
Ok(()) => Ok(()),
Err(mpsc::SendError(child)) => Err(finish_failed_handoff(
child,
io::Error::other("detached reaper stopped unexpectedly"),
)),
}
}
fn reap_loop(receiver: Receiver<Child>) {
let mut children = Vec::new();
loop {
if children.is_empty() {
match receiver.recv() {
Ok(child) => children.push(child),
Err(_) => return,
}
} else {
match receiver.recv_timeout(POLL_INTERVAL) {
Ok(child) => children.push(child),
Err(mpsc::RecvTimeoutError::Timeout) => {}
Err(mpsc::RecvTimeoutError::Disconnected) => {
for child in children {
let _ = wait_until_reaped(child);
}
return;
}
}
}
let mut index = 0;
while index < children.len() {
match children[index].try_wait() {
Ok(Some(_)) => {
let _ = children.swap_remove(index).wait();
}
Ok(None) | Err(_) => {
index += 1;
}
}
}
}
}
}
#[cfg(unix)]
pub(crate) fn prepare_reaper() -> std::io::Result<()> {
reaper::prepare()
}
#[cfg(unix)]
pub(crate) fn handoff_reaper(child: std::process::Child) -> std::io::Result<()> {
reaper::handoff(child)
}
#[derive(Debug)]
pub struct DetachedChild {
pid: u32,
}
impl DetachedChild {
pub(crate) fn new(pid: u32) -> Self {
Self { pid }
}
#[must_use]
pub fn pid(&self) -> u32 {
self.pid
}
}