use std::sync::Arc;
pub trait Signaller: Send + Sync + std::fmt::Debug {
fn signal(&self, leader_pid: u32, signal_name: &str) -> Result<bool, String>;
}
#[derive(Debug, Default)]
pub struct LocalSignaller;
impl Signaller for LocalSignaller {
#[cfg(unix)]
fn signal(&self, leader_pid: u32, signal_name: &str) -> Result<bool, String> {
let sig = match signal_name {
"SIGTERM" => libc::SIGTERM,
"SIGKILL" => libc::SIGKILL,
"SIGINT" => libc::SIGINT,
"SIGHUP" => libc::SIGHUP,
other => return Err(format!("unsupported signal: {}", other)),
};
let rc = unsafe { libc::kill(-(leader_pid as i32), sig) };
if rc == 0 {
Ok(true)
} else {
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::ESRCH) {
Ok(false)
} else {
Err(format!("kill(-{}, {}): {}", leader_pid, signal_name, err))
}
}
}
#[cfg(windows)]
fn signal(&self, _leader_pid: u32, signal_name: &str) -> Result<bool, String> {
Err(format!(
"Windows LocalSignaller cannot deliver {} — use TerminalManager::close()",
signal_name
))
}
}
#[derive(Debug, Clone)]
pub struct ProcessGroupEntry {
pub leader_pid: u32,
pub label: String,
}
#[derive(Debug)]
pub struct ProcessGroups {
signaller: Arc<dyn Signaller>,
entries: Vec<ProcessGroupEntry>,
}
impl ProcessGroups {
pub fn new(signaller: Arc<dyn Signaller>) -> Self {
Self {
signaller,
entries: Vec::new(),
}
}
pub fn register(&mut self, leader_pid: u32, label: impl Into<String>) {
let label = label.into();
if let Some(e) = self.entries.iter_mut().find(|e| e.leader_pid == leader_pid) {
e.label = label;
} else {
self.entries.push(ProcessGroupEntry { leader_pid, label });
}
}
pub fn forget(&mut self, leader_pid: u32) {
self.entries.retain(|e| e.leader_pid != leader_pid);
}
pub fn signal_all(
&mut self,
signal_name: &str,
) -> Vec<(ProcessGroupEntry, Result<bool, String>)> {
let mut out = Vec::with_capacity(self.entries.len());
let mut dead: Vec<u32> = Vec::new();
for e in &self.entries {
let r = self.signaller.signal(e.leader_pid, signal_name);
if matches!(r, Ok(false)) {
dead.push(e.leader_pid);
}
out.push((e.clone(), r));
}
self.entries.retain(|e| !dead.contains(&e.leader_pid));
out
}
pub fn set_signaller(&mut self, signaller: Arc<dyn Signaller>) {
self.signaller = signaller;
}
pub fn entries(&self) -> &[ProcessGroupEntry] {
&self.entries
}
}
impl Default for ProcessGroups {
fn default() -> Self {
Self::new(Arc::new(LocalSignaller))
}
}