use std::collections::HashSet;
use std::sync::{Arc, Mutex};
#[derive(Clone, Default)]
pub struct LspChildRegistry {
inner: Arc<Mutex<HashSet<u32>>>,
}
impl LspChildRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn track(&self, pid: u32) {
if let Ok(mut set) = self.inner.lock() {
set.insert(pid);
}
}
pub fn untrack(&self, pid: u32) {
if let Ok(mut set) = self.inner.lock() {
set.remove(&pid);
}
}
pub fn pids(&self) -> Vec<u32> {
self.inner
.lock()
.map(|set| set.iter().copied().collect())
.unwrap_or_default()
}
#[cfg(unix)]
pub fn kill_all(&self) -> usize {
use std::os::raw::c_int;
let pids = self.pids();
let mut killed = 0;
for pid in pids {
unsafe {
let pid_t = pid as libc::pid_t;
let rc = libc::kill(pid_t, 9 as c_int);
if rc == 0 {
killed += 1;
}
}
}
killed
}
#[cfg(not(unix))]
pub fn kill_all(&self) -> usize {
let pids = self.pids();
let mut killed = 0;
for pid in pids {
if std::process::Command::new("taskkill")
.args(["/F", "/PID", &pid.to_string()])
.status()
.is_ok()
{
killed += 1;
}
}
killed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn track_untrack_pids_round_trip() {
let reg = LspChildRegistry::new();
reg.track(100);
reg.track(200);
let mut pids = reg.pids();
pids.sort();
assert_eq!(pids, vec![100, 200]);
reg.untrack(100);
assert_eq!(reg.pids(), vec![200]);
}
#[test]
fn clones_share_state() {
let a = LspChildRegistry::new();
let b = a.clone();
a.track(42);
assert_eq!(b.pids(), vec![42]);
b.untrack(42);
assert!(a.pids().is_empty());
}
#[test]
fn untracking_unknown_pid_is_safe() {
let reg = LspChildRegistry::new();
reg.untrack(999); assert!(reg.pids().is_empty());
}
#[test]
fn kill_all_with_no_pids_returns_zero() {
let reg = LspChildRegistry::new();
assert_eq!(reg.kill_all(), 0);
}
}