#[cfg(unix)]
pub fn is_process_alive(pid: u32) -> bool {
let Ok(pid) = i32::try_from(pid) else {
return false;
};
let result = unsafe { libc::kill(pid, 0) };
result == 0 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
}
#[cfg(windows)]
pub fn is_process_alive(pid: u32) -> bool {
use windows_sys::Win32::Foundation::{CloseHandle, STILL_ACTIVE};
use windows_sys::Win32::System::Threading::{
GetExitCodeProcess, OpenProcess, PROCESS_QUERY_INFORMATION,
};
unsafe {
let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
if handle == 0 {
return false;
}
let mut exit_code = 0u32;
let ok = GetExitCodeProcess(handle, &mut exit_code);
CloseHandle(handle);
ok != 0 && exit_code == STILL_ACTIVE as u32
}
}
#[cfg(not(any(unix, windows)))]
pub fn is_process_alive(_pid: u32) -> bool {
false
}
#[cfg(target_os = "linux")]
pub fn pid_start_time(pid: u32) -> Option<u64> {
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
linux_process_identity_from_stat(&stat).map(|(_, start_time)| start_time)
}
#[cfg(not(target_os = "linux"))]
pub fn pid_start_time(_pid: u32) -> Option<u64> {
None
}
pub fn is_process_alive_with_identity(pid: u32, expected_start_time: Option<u64>) -> bool {
if !is_process_alive(pid) {
return false;
}
match expected_start_time {
Some(expected) => pid_start_time(pid) == Some(expected),
None => true,
}
}
#[cfg(target_os = "linux")]
pub fn is_process_running_with_identity(pid: u32, expected_start_time: Option<u64>) -> bool {
let Ok(stat) = std::fs::read_to_string(format!("/proc/{pid}/stat")) else {
return false;
};
linux_process_identity_from_stat(&stat).is_some_and(|(state, start_time)| {
state != 'Z'
&& expected_start_time
.map(|expected| expected == start_time)
.unwrap_or(true)
})
}
#[cfg(not(target_os = "linux"))]
pub fn is_process_running_with_identity(pid: u32, expected_start_time: Option<u64>) -> bool {
is_process_alive_with_identity(pid, expected_start_time)
}
#[cfg(target_os = "linux")]
fn linux_process_identity_from_stat(stat: &str) -> Option<(char, u64)> {
let fields: Vec<&str> = stat
.get(stat.rfind(')')? + 1..)?
.split_whitespace()
.collect();
let state = fields.first()?.chars().next()?;
let start_time = fields.get(19)?.parse().ok()?;
Some((state, start_time))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_process_is_alive() {
assert!(is_process_alive(std::process::id()));
}
#[test]
fn missing_process_is_not_alive() {
assert!(!is_process_alive(0x7fff_fffe));
}
#[cfg(target_os = "linux")]
#[test]
fn parses_start_time_after_complex_command_name() {
let stat =
"123 (command (with) spaces) S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 4242";
assert_eq!(linux_process_identity_from_stat(stat), Some(('S', 4242)));
assert_eq!(linux_process_identity_from_stat("malformed"), None);
assert_eq!(linux_process_identity_from_stat("123 (short) S 1"), None);
}
#[cfg(target_os = "linux")]
#[test]
fn identity_rejects_a_reused_pid() {
let pid = std::process::id();
let start_time = pid_start_time(pid);
assert!(start_time.is_some());
assert!(is_process_alive_with_identity(pid, start_time));
assert!(!is_process_alive_with_identity(pid, Some(u64::MAX)));
assert!(is_process_alive_with_identity(pid, None));
assert!(!is_process_alive_with_identity(0x7fff_fffe, None));
assert!(is_process_running_with_identity(pid, start_time));
}
#[cfg(target_os = "linux")]
#[test]
fn parses_zombie_state_for_completion_waiters() {
let stat = "123 (completed worker) Z 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 4242";
assert_eq!(linux_process_identity_from_stat(stat), Some(('Z', 4242)));
}
#[cfg(target_os = "linux")]
#[test]
fn running_identity_treats_an_unreaped_child_as_finished() {
let mut child = std::process::Command::new("true").spawn().unwrap();
let pid = child.id();
let start_time = pid_start_time(pid).unwrap();
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(1);
while is_process_running_with_identity(pid, Some(start_time))
&& std::time::Instant::now() < deadline
{
std::thread::sleep(std::time::Duration::from_millis(5));
}
assert!(is_process_alive_with_identity(pid, Some(start_time)));
assert!(!is_process_running_with_identity(pid, Some(start_time)));
child.wait().unwrap();
}
}