aft/bash_background/
process.rs1use std::process::Child;
8#[cfg(windows)]
9use std::process::{Command, Stdio};
10#[cfg(unix)]
11use std::thread;
12use std::time::Duration;
13#[cfg(unix)]
14use std::time::Instant;
15
16pub const TERMINATE_GRACE: Duration = Duration::from_secs(2);
17
18#[cfg(unix)]
19pub fn terminate_process(child: &mut Child) {
20 let pgid = child.id() as i32;
21 terminate_pgid(pgid, Some(child));
22}
23
24#[cfg(unix)]
25pub fn terminate_pgid(pgid: i32, mut child: Option<&mut Child>) {
26 unsafe {
27 libc::killpg(pgid, libc::SIGTERM);
28 }
29 let grace_started = Instant::now();
30 while grace_started.elapsed() < TERMINATE_GRACE {
31 if let Some(child) = child.as_deref_mut() {
32 if matches!(child.try_wait(), Ok(Some(_))) {
33 break;
39 }
40 }
41 thread::sleep(Duration::from_millis(50));
42 }
43 unsafe {
44 libc::killpg(pgid, libc::SIGKILL);
45 }
46}
47
48#[cfg(windows)]
49pub fn terminate_process(child: &mut Child) {
50 terminate_pid(child.id());
51}
52
53#[cfg(windows)]
54pub fn terminate_pid(pid: u32) {
55 let pid = pid.to_string();
56 let _ = Command::new("taskkill")
57 .args(["/PID", &pid, "/T", "/F"])
58 .stdout(Stdio::null())
59 .stderr(Stdio::null())
60 .status();
61}
62
63#[cfg(unix)]
64pub fn is_process_alive(pid: u32) -> bool {
65 let Ok(pid) = i32::try_from(pid) else {
66 return false;
67 };
68 if pid <= 0 {
69 return false;
70 }
71 (unsafe { libc::kill(pid, 0) == 0 })
72 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
73}
74
75#[cfg(windows)]
76pub fn is_process_alive(pid: u32) -> bool {
77 use std::ffi::c_void;
78
79 type Handle = *mut c_void;
80
81 extern "system" {
82 fn OpenProcess(dwDesiredAccess: u32, bInheritHandle: i32, dwProcessId: u32) -> Handle;
83 fn GetExitCodeProcess(hProcess: Handle, lpExitCode: *mut u32) -> i32;
84 fn CloseHandle(hObject: Handle) -> i32;
85 }
86
87 const FALSE: i32 = 0;
88 const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;
89 const STILL_ACTIVE: u32 = 0x103;
90
91 if pid == 0 {
92 return false;
93 }
94
95 unsafe {
96 let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
97 if handle.is_null() {
98 return false;
99 }
100 let mut exit_code = 0;
101 let ok = GetExitCodeProcess(handle, &mut exit_code) != 0 && exit_code == STILL_ACTIVE;
102 let _ = CloseHandle(handle);
103 ok
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn is_process_alive_returns_true_for_self() {
113 assert!(is_process_alive(std::process::id()));
114 }
115
116 #[test]
117 fn is_process_alive_returns_false_for_dead_pid() {
118 #[cfg(unix)]
119 let mut child = std::process::Command::new("/bin/sh")
120 .args(["-c", "true"])
121 .spawn()
122 .expect("spawn true");
123
124 #[cfg(windows)]
125 let mut child = std::process::Command::new("cmd.exe")
126 .args(["/D", "/C", "exit 0"])
127 .spawn()
128 .expect("spawn cmd");
129
130 let pid = child.id();
131 child.wait().expect("wait for child");
132
133 assert!(!is_process_alive(pid));
134 }
135
136 #[cfg(unix)]
142 #[test]
143 fn terminate_pgid_kills_term_ignoring_descendant_after_leader_exits() {
144 use std::os::unix::process::CommandExt;
145
146 let dir = tempfile::tempdir().unwrap();
147 let pidfile = dir.path().join("desc.pid");
148 let ready = dir.path().join("ready");
149
150 let script = format!(
157 "sh -c \"trap '' TERM; echo \\$$ > '{pid}'; touch '{ready}'; sleep 30\" & \
158 while [ ! -f '{ready}' ]; do sleep 0.02; done; exit 0",
159 pid = pidfile.display(),
160 ready = ready.display(),
161 );
162 let mut leader = unsafe {
163 std::process::Command::new("/bin/sh")
164 .args(["-c", &script])
165 .pre_exec(|| {
166 libc::setsid();
167 Ok(())
168 })
169 .spawn()
170 .expect("spawn leader")
171 };
172 let pgid = leader.id() as i32;
173
174 let start = Instant::now();
176 while !ready.exists() && start.elapsed() < Duration::from_secs(5) {
177 thread::sleep(Duration::from_millis(20));
178 }
179 let desc_pid: u32 = std::fs::read_to_string(&pidfile)
180 .expect("descendant pid file")
181 .trim()
182 .parse()
183 .expect("parse descendant pid");
184 assert!(is_process_alive(desc_pid), "descendant should be alive");
185
186 terminate_pgid(pgid, Some(&mut leader));
187
188 let start = Instant::now();
190 while is_process_alive(desc_pid) && start.elapsed() < Duration::from_secs(5) {
191 thread::sleep(Duration::from_millis(20));
192 }
193 assert!(
194 !is_process_alive(desc_pid),
195 "TERM-ignoring descendant must be SIGKILLed when the group is terminated"
196 );
197 }
198}