planarian_backend_linux/
process.rs1use planarian_schema::{Clock, Millis, ProcessBackend, Timestamp};
4use rustix::process::{Pid, Signal, kill_process_group, test_kill_process};
5use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
6
7#[derive(Clone, Copy, Debug, Default)]
8pub struct Processes;
9
10pub const KILL_GRACE: Duration = Duration::from_secs(5);
11
12impl ProcessBackend for Processes {
13 fn kill_group(&self, pid: u32) -> bool {
15 let Some(p) = Pid::from_raw(pid as i32) else { return true };
16 if test_kill_process(p).is_err() {
17 return true;
18 }
19 let _ = kill_process_group(p, Signal::TERM);
20 let deadline = Instant::now() + KILL_GRACE;
21 while Instant::now() < deadline {
22 if test_kill_process(p).is_err() {
23 return true;
24 }
25 std::thread::sleep(Duration::from_millis(50));
26 }
27 let _ = kill_process_group(p, Signal::KILL);
28 std::thread::sleep(Duration::from_millis(200));
29 test_kill_process(p).is_err()
30 }
31
32 fn alive(&self, pid: u32) -> bool {
33 Pid::from_raw(pid as i32).is_some_and(|p| test_kill_process(p).is_ok())
34 }
35}
36
37fn boottime_ms() -> u64 {
38 let t = rustix::time::clock_gettime(rustix::time::ClockId::Boottime);
39 t.tv_sec as u64 * 1000 + t.tv_nsec as u64 / 1_000_000
40}
41
42static ANCHOR: std::sync::OnceLock<(u64, u64)> = std::sync::OnceLock::new();
44
45impl Clock for Processes {
46 fn now(&self) -> Timestamp {
52 let (wall0, boot0) = *ANCHOR.get_or_init(|| {
53 let wall = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_millis() as u64).unwrap_or(0);
54 (wall, boottime_ms())
55 });
56 Timestamp(wall0.saturating_add(boottime_ms().saturating_sub(boot0)))
57 }
58
59 fn boot_now(&self) -> Timestamp {
60 Timestamp(boottime_ms())
61 }
62
63 fn sleep(&self, d: Millis) {
64 std::thread::sleep(Duration::from_millis(d.0));
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn kills_a_group() {
74 let mut c = std::process::Command::new("sh");
75 c.args(["-c", "sleep 30 & sleep 30"]);
76 std::os::unix::process::CommandExt::process_group(&mut c, 0);
77 let mut child = c.spawn().unwrap();
78 let pid = child.id();
79 assert!(Processes.alive(pid));
80 let reaper = std::thread::spawn(move || child.wait());
81 assert!(Processes.kill_group(pid));
82 let _ = reaper.join();
83 assert!(!Processes.alive(pid));
84 }
85}