use std::path::{Path, PathBuf};
use std::process::{Child, Command, ExitStatus};
use std::time::{Duration, Instant};
pub struct App {
child: Child,
started: Instant,
binary: PathBuf,
}
impl App {
pub fn start(binary: &Path, root: &Path, runs: &Path, generation: u64) -> std::io::Result<App> {
std::fs::create_dir_all(runs)?;
let name = binary.file_name().unwrap_or_default().to_string_lossy();
let copy = runs.join(format!("{generation}-{name}"));
std::fs::copy(binary, ©)?;
let child = Command::new(©).current_dir(root).spawn()?;
Ok(App {
child,
started: Instant::now(),
binary: copy,
})
}
pub fn uptime(&self) -> Duration {
self.started.elapsed()
}
pub fn exited(&mut self) -> Option<ExitStatus> {
self.child.try_wait().ok().flatten()
}
pub fn stop(mut self, grace: Duration) {
terminate(&self.child);
let deadline = Instant::now() + grace;
while Instant::now() < deadline {
if self.exited().is_some() {
break;
}
std::thread::sleep(Duration::from_millis(20));
}
let _ = self.child.kill();
let _ = self.child.wait();
let _ = std::fs::remove_file(&self.binary);
}
}
#[cfg(unix)]
fn terminate(child: &Child) {
unsafe {
libc::kill(child.id() as libc::pid_t, libc::SIGTERM);
}
}
#[cfg(not(unix))]
fn terminate(_: &Child) {
}
pub fn describe(status: ExitStatus) -> String {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
if let Some(signal) = status.signal() {
return format!("signal {signal}");
}
}
match status.code() {
Some(code) => format!("exit code {code}"),
None => "no exit code".to_string(),
}
}