use libc::{_exit, nfds_t, poll, pollfd, EXIT_FAILURE, POLLHUP};
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
use nix::unistd::Pid;
use std::os::fd::RawFd;
use crate::timeout::TimeoutManager;
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
pub enum ReapError {
#[error("Timeout waiting for child process to exit")]
Timeout,
#[error("Error waiting for child process to exit: {0}")]
WaitError(#[from] nix::Error),
}
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
pub enum PollError {
#[error("Poll failed with errno: {0}")]
PollError(i32),
#[error("Poll returned unexpected result: revents = {0}")]
UnexpectedResult(i16),
}
pub fn reap_child_non_blocking(
pid: Pid,
timeout_manager: &TimeoutManager,
) -> Result<bool, ReapError> {
loop {
match waitpid(pid, Some(WaitPidFlag::WNOHANG)) {
Ok(WaitStatus::StillAlive) => {
if timeout_manager.elapsed() > timeout_manager.timeout() {
return Err(ReapError::Timeout);
}
}
Ok(_status) => return Ok(true),
Err(nix::Error::ECHILD) => {
return Ok(false);
}
Err(e) => return Err(ReapError::WaitError(e)),
}
}
}
pub fn terminate() -> ! {
unsafe { _exit(EXIT_FAILURE) }
}
pub fn wait_for_pollhup(
target_fd: RawFd,
timeout_manager: &TimeoutManager,
) -> Result<bool, PollError> {
let mut poll_fds = [pollfd {
fd: target_fd,
events: POLLHUP,
revents: 0,
}];
loop {
let timeout_ms = timeout_manager.remaining().as_millis() as i32;
let poll_result =
unsafe { poll(poll_fds.as_mut_ptr(), poll_fds.len() as nfds_t, timeout_ms) };
match poll_result {
-1 => {
match nix::Error::last_raw() {
libc::EAGAIN | libc::EINTR => {
continue;
}
errno => return Err(PollError::PollError(errno)),
}
}
0 => return Ok(false), _ => {
let revents = poll_fds[0].revents;
if revents & POLLHUP != 0 {
return Ok(true); } else {
return Err(PollError::UnexpectedResult(revents));
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[cfg_attr(miri, ignore)] #[test]
fn test_reap_child_non_blocking_timeout() {
let timeout = Duration::from_millis(10);
let manager = TimeoutManager::new(timeout);
let result = reap_child_non_blocking(Pid::from_raw(99999), &manager);
assert!(matches!(result, Ok(false)));
}
#[cfg_attr(miri, ignore)] #[test]
fn test_reap_child_non_blocking_exited_child() {
let timeout = Duration::from_secs(1);
let manager = TimeoutManager::new(timeout);
let result = reap_child_non_blocking(Pid::from_raw(99999), &manager);
assert!(matches!(result, Ok(false)));
}
#[cfg_attr(miri, ignore)] #[test]
fn test_reap_child_non_blocking_nonexistent_pid() {
let timeout = Duration::from_secs(1);
let manager = TimeoutManager::new(timeout);
let result = reap_child_non_blocking(Pid::from_raw(99999), &manager);
assert!(matches!(result, Ok(false)));
}
#[cfg_attr(miri, ignore)] #[test]
fn test_wait_for_pollhup_timeout() {
let timeout = Duration::from_millis(10);
let manager = TimeoutManager::new(timeout);
let result = wait_for_pollhup(-1, &manager);
assert!(matches!(result, Ok(false)));
}
#[cfg_attr(miri, ignore)] #[test]
fn test_wait_for_pollhup_invalid_fd() {
let timeout = Duration::from_secs(1);
let manager = TimeoutManager::new(timeout);
let invalid_fd = 999_999;
let result = wait_for_pollhup(invalid_fd, &manager);
match result {
Err(PollError::PollError(errno)) => {
assert!(errno > 0);
}
Err(PollError::UnexpectedResult(revents)) => {
println!("wait_for_pollhup({invalid_fd}, ..) returned UnexpectedResult({revents}) as allowed on this platform");
}
_ => panic!("Expected error for invalid FD, got: {result:?}"),
}
}
}