use crate::{process_handle::ProcessHandle, TerminationOptions};
use std::{
io,
sync::{Arc, Mutex, MutexGuard, Weak},
};
#[cfg(unix)]
type PlatformChild = crate::unix_child::UnixChild;
#[cfg(windows)]
type PlatformChild = crate::spawn_windows::WindowsChild;
struct Entry {
key: usize,
pid: u32,
child: Weak<Mutex<PlatformChild>>,
#[cfg(windows)]
process: ProcessHandle,
#[cfg(windows)]
job: Weak<crate::process_windows::KillOnCloseJob>,
}
static OWNED: Mutex<Vec<Entry>> = Mutex::new(Vec::new());
fn owned() -> MutexGuard<'static, Vec<Entry>> {
OWNED.lock().unwrap_or_else(|poison| poison.into_inner())
}
fn key_of(child: &Arc<Mutex<PlatformChild>>) -> usize {
Arc::as_ptr(child) as usize
}
#[cfg(unix)]
pub(crate) fn register(child: &Arc<Mutex<PlatformChild>>, pid: u32) {
let mut list = owned();
list.retain(|entry| entry.child.strong_count() > 0);
list.push(Entry {
key: key_of(child),
pid,
child: Arc::downgrade(child),
});
}
#[cfg(windows)]
pub(crate) fn register(
child: &Arc<Mutex<PlatformChild>>,
handle: &ProcessHandle,
job: Weak<crate::process_windows::KillOnCloseJob>,
pid: u32,
) {
let Ok(process) = handle.try_clone() else {
return;
};
let mut list = owned();
list.retain(|entry| entry.child.strong_count() > 0);
list.push(Entry {
key: key_of(child),
pid,
child: Arc::downgrade(child),
process,
job,
});
}
pub(crate) fn forget(child: &Arc<Mutex<PlatformChild>>) {
let key = key_of(child);
owned().retain(|entry| entry.key != key);
}
pub fn owned_count() -> usize {
let mut list = owned();
list.retain(|entry| entry.child.strong_count() > 0);
list.len()
}
#[derive(Debug, Default)]
pub struct TerminateAllReport {
pub terminated: Vec<u32>,
pub already_gone: Vec<u32>,
pub failed: Vec<(u32, io::Error)>,
}
pub fn terminate_all_owned(options: TerminationOptions) -> TerminateAllReport {
#[cfg(unix)]
{
terminate_all_unix(options)
}
#[cfg(windows)]
{
terminate_all_windows(options)
}
}
#[cfg(unix)]
fn terminate_all_unix(options: TerminationOptions) -> TerminateAllReport {
use crate::{
owned::{await_tree_shared, signal_shared},
unix_child::{SignalOutcome, SignalTarget},
};
use std::time::{Duration, Instant, SystemTime};
const REAP_BUDGET: Duration = Duration::from_secs(2);
let live: Vec<(u32, Arc<Mutex<PlatformChild>>)> = {
let mut list = owned();
list.retain(|entry| entry.child.strong_count() > 0);
list.iter()
.filter_map(|entry| entry.child.upgrade().map(|child| (entry.pid, child)))
.collect()
};
let mut report = TerminateAllReport::default();
let mut asked: Vec<(u32, Arc<Mutex<PlatformChild>>, ProcessHandle)> = Vec::new();
for (pid, child) in live {
let handle = match ProcessHandle::new(&child, SystemTime::now()) {
Ok(handle) => handle,
Err(error) => {
report.failed.push((pid, error));
continue;
}
};
match signal_shared(&child, SignalTarget::Group, libc::SIGTERM) {
Ok(SignalOutcome::Sent) => asked.push((pid, child, handle)),
Ok(_) => report.already_gone.push(pid),
Err(error) => report.failed.push((pid, error)),
}
}
let deadline = Instant::now().checked_add(options.grace);
for (pid, child, handle) in &asked {
let left = deadline.map_or(Duration::ZERO, |at| {
at.saturating_duration_since(Instant::now())
});
if let Err(error) = await_tree_shared(child, handle, left, true) {
report.failed.push((*pid, error));
}
}
let reap_deadline = Instant::now().checked_add(REAP_BUDGET);
for (pid, child, handle) in &asked {
match signal_shared(child, SignalTarget::Group, libc::SIGKILL) {
Ok(_) => {
let left = reap_deadline.map_or(Duration::ZERO, |at| {
at.saturating_duration_since(Instant::now())
});
let _ = handle.wait_timeout(left);
if !report.failed.iter().any(|(failed, _)| failed == pid) {
report.terminated.push(*pid);
}
}
Err(error) => report.failed.push((*pid, error)),
}
}
report
}
#[cfg(windows)]
fn terminate_all_windows(options: TerminationOptions) -> TerminateAllReport {
let live: Vec<(
u32,
ProcessHandle,
Option<Arc<crate::process_windows::KillOnCloseJob>>,
)> = {
let mut list = owned();
list.retain(|entry| entry.child.strong_count() > 0);
list.iter()
.filter_map(|entry| {
Some((
entry.pid,
entry.process.try_clone().ok()?,
entry.job.upgrade(),
))
})
.collect()
};
let mut report = TerminateAllReport::default();
for (pid, process, job) in live {
if job.is_none() && matches!(process.try_wait(), Ok(Some(_))) {
report.already_gone.push(pid);
continue;
}
match crate::process_windows::terminate(&process, job.as_deref(), options.windows_exit_code)
{
Ok(()) => report.terminated.push(pid),
Err(error) => report.failed.push((pid, error)),
}
}
report
}