use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[cfg(unix)]
use std::sync::LazyLock;
#[cfg(unix)]
static ACTIVE_PGIDS: LazyLock<Arc<Mutex<HashSet<i32>>>> =
LazyLock::new(|| Arc::new(Mutex::new(HashSet::new())));
#[cfg(unix)]
pub fn register_pgid(pgid: i32) {
if pgid > 0 {
if let Ok(mut set) = ACTIVE_PGIDS.lock() {
set.insert(pgid);
tracing::debug!("Registered process group: {}", pgid);
}
}
}
#[cfg(not(unix))]
pub fn register_pgid(_pgid: i32) {}
#[cfg(unix)]
pub fn unregister_pgid(pgid: i32) {
if let Ok(mut set) = ACTIVE_PGIDS.lock() {
set.remove(&pgid);
tracing::debug!("Unregistered process group: {}", pgid);
}
}
#[cfg(not(unix))]
pub fn unregister_pgid(_pgid: i32) {}
#[cfg(unix)]
pub fn terminate_process_group(pgid: i32) -> Result<(), String> {
if pgid <= 0 {
return Err(format!("Invalid process group ID: {}", pgid));
}
tracing::debug!("Terminating process group: {}", pgid);
let result = unsafe { libc::kill(-pgid, libc::SIGTERM) };
if result != 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::ESRCH) {
tracing::warn!("Failed to send SIGTERM to process group {}: {}", pgid, err);
}
}
std::thread::sleep(Duration::from_millis(300));
let result = unsafe { libc::kill(-pgid, libc::SIGKILL) };
if result != 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::ESRCH) {
tracing::warn!("Failed to send SIGKILL to process group {}: {}", pgid, err);
}
}
unregister_pgid(pgid);
Ok(())
}
#[cfg(not(unix))]
pub fn terminate_process_group(_pgid: i32) -> Result<(), String> {
Err("Process groups are only supported on Unix systems".to_string())
}
#[cfg(unix)]
pub fn send_sigterm_to_group(pgid: i32) {
if pgid <= 0 {
return;
}
let result = unsafe { libc::kill(-pgid, libc::SIGTERM) };
if result != 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::ESRCH) {
tracing::warn!("Failed to send SIGTERM to process group {}: {}", pgid, err);
}
}
}
#[cfg(unix)]
pub fn send_sigkill_to_group(pgid: i32) {
if pgid <= 0 {
return;
}
let result = unsafe { libc::kill(-pgid, libc::SIGKILL) };
if result != 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::ESRCH) {
tracing::warn!("Failed to send SIGKILL to process group {}: {}", pgid, err);
}
}
unregister_pgid(pgid);
}
#[cfg(unix)]
pub fn kill_all_process_groups() {
let pgids: Vec<i32> = {
if let Ok(mut set) = ACTIVE_PGIDS.lock() {
let pgids: Vec<i32> = set.iter().cloned().collect();
set.clear();
pgids
} else {
return;
}
};
if !pgids.is_empty() {
tracing::info!("Killing {} active process group(s)", pgids.len());
}
for pgid in pgids {
let _ = terminate_process_group(pgid);
}
}
#[cfg(not(unix))]
pub fn kill_all_process_groups() {}
#[cfg(unix)]
pub struct ProcessGroupCleanupGuard;
#[cfg(unix)]
impl Drop for ProcessGroupCleanupGuard {
fn drop(&mut self) {
kill_all_process_groups();
}
}
#[cfg(not(unix))]
pub struct ProcessGroupCleanupGuard;
#[cfg(not(unix))]
impl Drop for ProcessGroupCleanupGuard {
fn drop(&mut self) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(unix)]
fn test_register_unregister_pgid() {
register_pgid(12345);
{
if let Ok(set) = ACTIVE_PGIDS.lock() {
assert!(set.contains(&12345));
}
}
unregister_pgid(12345);
{
if let Ok(set) = ACTIVE_PGIDS.lock() {
assert!(!set.contains(&12345));
}
}
}
#[test]
#[cfg(unix)]
fn test_kill_all_process_groups() {
register_pgid(11111);
register_pgid(22222);
{
if let Ok(set) = ACTIVE_PGIDS.lock() {
assert_eq!(set.len(), 2);
}
}
kill_all_process_groups();
{
if let Ok(set) = ACTIVE_PGIDS.lock() {
assert!(set.is_empty());
}
}
}
#[test]
fn test_terminate_invalid_pgid() {
let result = terminate_process_group(-1);
#[cfg(unix)]
assert!(result.is_err());
}
}