use std::{io, mem::MaybeUninit, os::fd::OwnedFd, ptr, time::Duration};
use rustix::{
event::kqueue::{Event, EventFilter, EventFlags, ProcessEvents, kevent, kqueue},
io::{Errno, FdFlags, fcntl_setfd},
process::Pid,
};
use crate::{AsyncIo, Mode, Runtime};
pub(crate) enum Exit<M>
where
M: Mode,
{
Watched(AsyncIo<OwnedFd, M>),
Exited(Runtime<M>),
}
impl<M> Exit<M>
where
M: Mode,
{
pub(crate) fn new(runtime: &Runtime<M>, child: &std::process::Child) -> io::Result<Self> {
let queue = kqueue()?;
fcntl_setfd(&queue, FdFlags::CLOEXEC)?;
if !watch(&queue, Pid::from_child(child))? {
return Ok(Self::Exited(runtime.clone()));
}
Ok(Self::Watched(AsyncIo::from_nonblocking(runtime, queue)?))
}
pub(crate) async fn wait(&mut self, _child: &std::process::Child) -> io::Result<()> {
match self {
Self::Watched(queue) => {
queue.readable().await?;
let runtime = queue.runtime();
*self = Self::Exited(runtime);
Ok(())
}
Self::Exited(runtime) => {
runtime.sleep(RECHECK).await;
Ok(())
}
}
}
}
fn watch(queue: &OwnedFd, pid: Pid) -> io::Result<bool> {
let change = Event::new(
EventFilter::Proc {
pid,
flags: ProcessEvents::EXIT,
},
EventFlags::ADD | EventFlags::RECEIPT,
ptr::null_mut(),
);
let mut receipt = [MaybeUninit::uninit(); 1];
let (receipts, _) = unsafe { kevent(queue, &[change], &mut receipt, Some(Duration::ZERO)) }?;
debug_assert_eq!(receipts.len(), 1, "the change comes back as a receipt");
let Some(receipt) = receipts
.first()
.filter(|receipt| receipt.flags().contains(EventFlags::ERROR))
else {
return Ok(true);
};
match receipt.data() {
0 => Ok(true),
errno => match Errno::from_raw_os_error(errno as i32) {
Errno::SRCH => Ok(false),
errno => Err(errno.into()),
},
}
}
const RECHECK: Duration = Duration::from_millis(1);