#[cfg(target_os = "linux")]
use std::os::fd::OwnedFd;
#[cfg(windows)]
use std::os::windows::io::{AsHandle, AsRawHandle};
use std::{io, marker::PhantomData};
#[cfg(target_os = "linux")]
use rustix::process::{PidfdFlags, pidfd_open};
#[cfg(unix)]
use rustix::{
io::Errno,
process::{Pid, WaitId, WaitIdOptions, waitid},
};
#[cfg(windows)]
use windows_sys::Win32::{
Foundation::WAIT_FAILED,
System::Threading::{INFINITE, WaitForSingleObject},
};
#[cfg(target_os = "linux")]
use crate::AsyncIo;
use crate::{BlockingWork, Mode, Runtime, process::WAITS, unblock::unblock_on};
pub(crate) enum Exit<M>
where
M: Mode,
{
#[cfg(target_os = "linux")]
Watched(AsyncIo<OwnedFd, M>),
Pool {
work: Option<BlockingWork<io::Result<()>>>,
_mode: PhantomData<M::Ptr<()>>,
},
}
impl<M> Exit<M>
where
M: Mode,
{
#[cfg(target_os = "linux")]
pub(crate) fn new(runtime: &Runtime<M>, child: &std::process::Child) -> io::Result<Self> {
let Ok(pidfd) = pidfd_open(Pid::from_child(child), PidfdFlags::empty()) else {
return Ok(Self::pool());
};
Ok(Self::Watched(AsyncIo::from_nonblocking(runtime, pidfd)?))
}
#[cfg(not(target_os = "linux"))]
pub(crate) fn new(_runtime: &Runtime<M>, _child: &std::process::Child) -> io::Result<Self> {
Ok(Self::pool())
}
pub(crate) async fn wait(&mut self, child: &std::process::Child) -> io::Result<()> {
match self {
#[cfg(target_os = "linux")]
Self::Watched(pidfd) => {
pidfd.readable().await?;
*self = Self::pool();
Ok(())
}
Self::Pool { work, .. } => {
let running = match &mut *work {
Some(running) => running,
none @ None => none.insert(start(child)?),
};
let outcome = running.await;
*work = None;
outcome
}
}
}
fn pool() -> Self {
Self::Pool {
work: None,
_mode: PhantomData,
}
}
}
#[cfg(unix)]
fn start(child: &std::process::Child) -> io::Result<BlockingWork<io::Result<()>>> {
let pid = Pid::from_child(child);
Ok(unblock_on(&WAITS, move || {
loop {
match waitid(
WaitId::Pid(pid),
WaitIdOptions::EXITED | WaitIdOptions::NOWAIT,
) {
Err(Errno::INTR) => {}
Ok(_) => return Ok(()),
Err(error) => return Err(error.into()),
}
}
}))
}
#[cfg(windows)]
fn start(child: &std::process::Child) -> io::Result<BlockingWork<io::Result<()>>> {
let handle = child.as_handle().try_clone_to_owned()?;
Ok(unblock_on(&WAITS, move || {
let waited = unsafe { WaitForSingleObject(handle.as_raw_handle(), INFINITE) };
if waited == WAIT_FAILED {
return Err(io::Error::last_os_error());
}
Ok(())
}))
}