use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
#[cfg(unix)]
const GRACE: std::time::Duration = std::time::Duration::from_secs(2);
#[derive(Clone, Default)]
pub struct Cancel {
inner: Arc<Inner>,
}
#[derive(Default)]
struct Inner {
cancelled: AtomicBool,
group: Mutex<Option<u32>>,
}
impl Cancel {
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.inner.cancelled.store(true, Ordering::SeqCst);
let group = *self.inner.group.lock().expect("cancel mutex");
if let Some(pgid) = group {
self.signal_group(pgid);
}
}
pub fn is_cancelled(&self) -> bool {
self.inner.cancelled.load(Ordering::SeqCst)
}
pub(crate) fn entered(&self, pgid: u32) {
let mut group = self.inner.group.lock().expect("cancel mutex");
*group = Some(pgid);
drop(group);
if self.is_cancelled() {
self.signal_group(pgid);
}
}
pub(crate) fn left(&self) {
*self.inner.group.lock().expect("cancel mutex") = None;
}
#[cfg(unix)]
fn signal_group(&self, pgid: u32) {
unsafe { libc::killpg(pgid as libc::pid_t, libc::SIGTERM) };
let inner = Arc::clone(&self.inner);
std::thread::spawn(move || {
std::thread::sleep(GRACE);
let still = *inner.group.lock().expect("cancel mutex");
if still == Some(pgid) {
unsafe { libc::killpg(pgid as libc::pid_t, libc::SIGKILL) };
}
});
}
#[cfg(not(unix))]
fn signal_group(&self, _pgid: u32) {}
}
impl std::fmt::Debug for Cancel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Cancel")
.field("cancelled", &self.is_cancelled())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fresh_handle_is_not_cancelled() {
assert!(!Cancel::new().is_cancelled());
}
#[test]
fn cancelling_is_visible_through_a_clone() {
let a = Cancel::new();
let b = a.clone();
a.cancel();
assert!(b.is_cancelled(), "the clone shares the state");
}
#[test]
fn cancelling_twice_is_harmless() {
let c = Cancel::new();
c.cancel();
c.cancel();
assert!(c.is_cancelled());
}
#[test]
fn cancelling_with_nothing_running_is_fine() {
let c = Cancel::new();
c.cancel();
c.left();
assert!(c.is_cancelled());
}
}