use std::{
io,
process::{Child, ExitStatus},
};
#[cfg(feature = "local")]
use crate::local::ProcessHandle;
pub struct Candidate {
process: Option<Child>,
selected: bool,
#[cfg(feature = "local")]
exit: Option<ProcessHandle>,
}
impl Candidate {
pub fn new(process: Child) -> Self {
Self {
#[cfg(feature = "local")]
exit: ProcessHandle::for_child(&process).ok(),
process: Some(process),
selected: false,
}
}
pub fn id(&self) -> u32 {
self.process.as_ref().expect("candidate owns child").id()
}
pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
self.process
.as_mut()
.expect("candidate owns child")
.try_wait()
}
#[cfg(feature = "local")]
pub fn exit_handle(&mut self) -> io::Result<&mut ProcessHandle> {
self.exit
.as_mut()
.ok_or_else(|| io::Error::other("the candidate's exit handle could not be opened"))
}
#[cfg(feature = "local")]
pub fn wait_until(&mut self, deadline: std::time::Instant) -> io::Result<Option<ExitStatus>> {
let process = self.process.as_mut().expect("candidate owns child");
if !crate::local::wait_child(|| process.try_wait(), self.exit.as_mut(), deadline)? {
return Ok(None);
}
process.wait().map(Some)
}
pub fn selected(&mut self) {
self.selected = true;
}
}
impl Drop for Candidate {
fn drop(&mut self) {
let Some(mut process) = self.process.take() else {
return;
};
if process.try_wait().is_ok_and(|status| status.is_some()) {
return;
}
if !self.selected && process.kill().is_ok() {
let _ = process.wait();
} else {
let _ = std::thread::Builder::new()
.name("daemon-reaper".into())
.spawn(move || {
let _ = process.wait();
});
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "local")]
#[test]
fn a_candidate_watches_its_process_from_the_moment_it_is_taken() {
let child = std::process::Command::new(std::env::current_exe().unwrap())
.arg("--list")
.stdout(std::process::Stdio::null())
.spawn()
.unwrap();
let pid = child.id();
let mut candidate = super::Candidate::new(child);
assert!(candidate.exit.is_some());
assert_eq!(candidate.exit_handle().unwrap().pid(), pid);
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
assert!(candidate.wait_until(deadline).unwrap().unwrap().success());
}
}