use std::{
io,
os::fd::{AsRawFd, OwnedFd},
process,
};
#[cfg(windows)]
use crate::driver::backends::iocp::{IocpOperation, IocpSubmission};
#[cfg(target_os = "linux")]
use crate::driver::backends::iouring::{Submission as UringSubmission, UringOperation};
use crate::driver::ops::{Completion, Op};
use crate::runtime::local::CURRENT_DRIVER;
pub(crate) struct WaitProcess {
pidfd: OwnedFd,
child: process::Child,
}
impl Op<WaitProcess> {
pub(crate) fn wait_process(child: process::Child, pidfd: OwnedFd) -> io::Result<Op<WaitProcess>> {
let data = WaitProcess { pidfd, child };
CURRENT_DRIVER.with(|handle| handle.upgrade().expect("not in a runtime").submit_op(data))
}
}
#[cfg(target_os = "linux")]
unsafe impl UringOperation for WaitProcess {
type Output = io::Result<process::ExitStatus>;
fn submit(&mut self) -> UringSubmission {
use io_uring::{opcode, types};
opcode::PollAdd::new(types::Fd(self.pidfd.as_raw_fd()), libc::POLLIN as u32).build()
}
fn complete(mut self, completion: Completion) -> Self::Output {
reap_after_pidfd_ready(completion, || {
self.child.wait()
})
}
}
fn reap_after_pidfd_ready<T>(completion: Completion, reap: impl FnOnce() -> io::Result<T>) -> io::Result<T> {
completion.result?;
reap()
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
#[test]
fn failed_pidfd_poll_does_not_wait_for_child() {
let waited = Cell::new(false);
let error = reap_after_pidfd_ready(
Completion::new(Err(io::Error::from_raw_os_error(libc::ECANCELED))),
|| {
waited.set(true);
Ok(())
},
)
.expect_err("cancelled pidfd poll must fail");
assert_eq!(error.raw_os_error(), Some(libc::ECANCELED));
assert!(!waited.get(), "child wait must not run after a failed pidfd poll");
}
}