#![allow(dead_code)]
use std::ops::Deref;
use std::path::Path;
use std::time::{Duration, Instant};
use tempfile::TempDir;
const REAP_GRACE: Duration = Duration::from_secs(2);
pub struct TestHome {
dir: TempDir,
}
impl TestHome {
pub fn new() -> Self {
Self {
dir: TempDir::new().expect("create temp ORCHESTRATECTL_HOME"),
}
}
}
impl Default for TestHome {
fn default() -> Self {
Self::new()
}
}
impl Deref for TestHome {
type Target = TempDir;
fn deref(&self) -> &TempDir {
&self.dir
}
}
impl Drop for TestHome {
fn drop(&mut self) {
reap_supervisors_under(self.dir.path());
}
}
pub fn reap_supervisors_under(home: &Path) {
let pids: Vec<libc::pid_t> = scan_supervisor_pids(home)
.into_iter()
.filter(|&p| is_supervisor_process(p))
.collect();
if pids.is_empty() {
return;
}
let our_pgid = unsafe { libc::getpgrp() };
for &pid in &pids {
if process_gone(pid) {
continue;
}
signal_target(pid, our_pgid, libc::SIGTERM);
}
let deadline = Instant::now() + REAP_GRACE;
for &pid in &pids {
while !process_gone(pid) {
if Instant::now() >= deadline {
signal_target(pid, our_pgid, libc::SIGKILL);
break;
}
std::thread::sleep(Duration::from_millis(20));
}
}
}
fn signal_target(pid: libc::pid_t, our_pgid: libc::pid_t, sig: libc::c_int) {
let group = unsafe { libc::getpgid(pid) };
if group > 1 && group != our_pgid {
unsafe { libc::kill(-group, sig) };
} else {
unsafe { libc::kill(pid, sig) };
}
}
fn process_gone(pid: libc::pid_t) -> bool {
unsafe { libc::kill(pid, 0) != 0 }
}
fn is_supervisor_process(pid: libc::pid_t) -> bool {
let Ok(out) = std::process::Command::new("ps")
.args(["-o", "command=", "-p", &pid.to_string()])
.output()
else {
return false;
};
out.status.success()
&& String::from_utf8_lossy(&out.stdout).contains("orchestratectl supervise")
}
fn scan_supervisor_pids(home: &Path) -> Vec<libc::pid_t> {
let mut pids = Vec::new();
let Ok(entries) = std::fs::read_dir(home.join("runs")) else {
return pids;
};
for entry in entries.flatten() {
let pid_file = entry.path().join("supervisor.pid");
if let Some(pid) = read_first_token_pid(&pid_file) {
if pid > 0 && !pids.contains(&pid) {
pids.push(pid);
}
}
}
pids
}
fn read_first_token_pid(path: &Path) -> Option<libc::pid_t> {
let s = std::fs::read_to_string(path).ok()?;
s.split_whitespace().next()?.parse::<libc::pid_t>().ok()
}