use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant};
use rustc_hash::FxHashMap;
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
static REGISTRY: OnceLock<Mutex<FxHashMap<u64, u32>>> = OnceLock::new();
static DRAINING: AtomicU64 = AtomicU64::new(0);
fn registry() -> &'static Mutex<FxHashMap<u64, u32>> {
REGISTRY.get_or_init(|| Mutex::new(FxHashMap::default()))
}
pub(super) fn register(pid: u32) -> u64 {
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(id, pid);
id
}
pub(super) fn deregister(id: u64) {
registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&id);
}
pub(super) fn drain_and_kill() {
if DRAINING
.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
return;
}
let pids: Vec<u32> = {
registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.drain()
.map(|(_id, pid)| pid)
.collect()
};
for pid in &pids {
kill_pid(*pid);
}
let deadline = Instant::now() + drain_budget();
while Instant::now() < deadline {
if !pids.iter().copied().any(pid_is_alive) {
return;
}
std::thread::sleep(Duration::from_millis(50));
}
}
#[cfg(unix)]
fn kill_pid(pid: u32) {
let _ = std::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
}
#[cfg(windows)]
fn kill_pid(pid: u32) {
use windows_sys::Win32::Foundation::{CloseHandle, FALSE, HANDLE};
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_TERMINATE, TerminateProcess};
unsafe {
let handle: HANDLE = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if handle.is_null() {
return;
}
let _ = TerminateProcess(handle, 1);
let _ = CloseHandle(handle);
}
}
#[cfg(not(any(unix, windows)))]
fn kill_pid(_pid: u32) {
}
#[cfg(unix)]
fn pid_is_alive(pid: u32) -> bool {
std::process::Command::new("kill")
.args(["-0", &pid.to_string()])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|s| s.success())
}
#[cfg(windows)]
fn pid_is_alive(pid: u32) -> bool {
use windows_sys::Win32::Foundation::{CloseHandle, FALSE, HANDLE, WAIT_OBJECT_0};
use windows_sys::Win32::System::Threading::{
OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION, WaitForSingleObject,
};
unsafe {
let handle: HANDLE = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if handle.is_null() {
return false;
}
let result = WaitForSingleObject(handle, 0);
let _ = CloseHandle(handle);
result != WAIT_OBJECT_0
}
}
#[cfg(not(any(unix, windows)))]
fn pid_is_alive(_pid: u32) -> bool {
false
}
#[cfg(unix)]
const fn drain_budget() -> Duration {
Duration::from_millis(500)
}
#[cfg(windows)]
const fn drain_budget() -> Duration {
Duration::from_millis(1500)
}
#[cfg(not(any(unix, windows)))]
const fn drain_budget() -> Duration {
Duration::from_millis(500)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn register_deregister_roundtrip() {
let id = register(42);
assert!(id > 0);
deregister(id);
deregister(id);
}
#[test]
fn ids_are_monotonic() {
let a = register(100);
let b = register(200);
assert!(b > a);
deregister(a);
deregister(b);
}
}