use std::os::unix::io::AsRawFd;
use std::process::Child;
use std::time::Duration;
use crate::errors::{Result, StandbyError};
pub fn wait_with_timerfd_timeout(
mut child: Child,
timeout: Duration,
) -> Result<std::process::ExitStatus> {
use nix::poll::{PollFd, PollFlags, poll};
use nix::sys::timerfd::{ClockId, TimerFd, TimerSetTimeFlags};
let tfd = TimerFd::new(ClockId::CLOCK_MONOTONIC, nix::fcntl::OFlag::O_NONBLOCK)
.map_err(|e| StandbyError::ProcessError(format!("Failed to create timerfd: {}", e)))?;
tfd.set(
nix::sys::timerfd::Expiration::OneShot(timeout),
TimerSetTimeFlags::empty(),
)
.map_err(|e| StandbyError::ProcessError(format!("Failed to set timer: {}", e)))?;
let child_fd = child.as_raw_fd();
let tfd_fd = tfd.as_raw_fd();
let mut fds = [
PollFd::new(child_fd, PollFlags::empty()), PollFd::new(tfd_fd, PollFlags::POLLIN), ];
loop {
let poll_timeout = std::time::Duration::from_millis(100);
let poll_millis = poll_timeout.as_millis() as i32;
match nix::poll::poll(&mut fds, poll_millis) {
Ok(0) => {
match child.try_wait() {
Ok(Some(status)) => return Ok(status),
Ok(None) => continue, Err(e) => {
return Err(StandbyError::ProcessError(format!(
"Failed to wait for process: {}",
e
)));
}
}
}
Ok(_) => {
if fds[1].revents().contains(PollFlags::POLLIN) {
return Err(StandbyError::ProcessError("Process timeout".to_string()));
}
match child.try_wait() {
Ok(Some(status)) => return Ok(status),
Ok(None) => continue, Err(e) => {
return Err(StandbyError::ProcessError(format!(
"Failed to wait for process: {}",
e
)));
}
}
}
Err(e) => return Err(StandbyError::ProcessError(format!("Poll failed: {}", e))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timerfd_immediate_exit() {
let child = std::process::Command::new("true")
.spawn()
.expect("failed to spawn");
let result = wait_with_timerfd_timeout(child, Duration::from_secs(5));
assert!(result.is_ok());
}
#[test]
fn test_timerfd_timeout() {
let child = std::process::Command::new("sleep")
.arg("10")
.spawn()
.expect("failed to spawn");
let result = wait_with_timerfd_timeout(child, Duration::from_millis(100));
assert!(result.is_err());
}
}