use std::time::{Duration, Instant};
pub const DEFAULT_GRACE: Duration = Duration::from_secs(5);
pub const DEFAULT_KILL_GRACE: Duration = Duration::from_secs(2);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LadderAction {
Wait,
Term,
Kill,
Done,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Phase {
Cancel,
Term,
Kill,
Done,
}
#[derive(Debug)]
pub struct Ladder {
started: Instant,
grace: Duration,
kill_grace: Duration,
phase: Phase,
}
impl Ladder {
pub fn new(now: Instant, grace: Duration, kill_grace: Duration) -> Ladder {
Ladder {
started: now,
grace,
kill_grace,
phase: Phase::Cancel,
}
}
pub fn with_defaults(now: Instant) -> Ladder {
Ladder::new(now, DEFAULT_GRACE, DEFAULT_KILL_GRACE)
}
pub fn poll(&mut self, now: Instant, all_exited: bool, force: bool) -> LadderAction {
if all_exited {
self.phase = Phase::Done;
return LadderAction::Done;
}
if force && self.phase < Phase::Kill {
self.phase = Phase::Kill;
return LadderAction::Kill;
}
let elapsed = now.saturating_duration_since(self.started);
match self.phase {
Phase::Cancel if elapsed >= self.grace => {
self.phase = Phase::Term;
LadderAction::Term
}
Phase::Term if elapsed >= self.grace + self.kill_grace => {
self.phase = Phase::Kill;
LadderAction::Kill
}
Phase::Done => LadderAction::Done,
_ => LadderAction::Wait,
}
}
}
#[cfg(unix)]
pub fn signal_group(pgid: i32, sig: i32) {
if pgid > 1 {
unsafe {
libc::killpg(pgid, sig);
}
}
}
#[cfg(unix)]
pub fn term_group(pgid: i32) {
signal_group(pgid, libc::SIGTERM);
}
#[cfg(unix)]
pub fn kill_group(pgid: i32) {
signal_group(pgid, libc::SIGKILL);
}
#[cfg(not(unix))]
pub fn signal_group(_pgid: i32, _sig: i32) {}
#[cfg(not(unix))]
pub fn term_group(_pgid: i32) {}
#[cfg(not(unix))]
pub fn kill_group(_pgid: i32) {}
#[cfg(test)]
mod tests {
use super::*;
fn ladder(now: Instant) -> Ladder {
Ladder::new(now, Duration::from_secs(5), Duration::from_secs(2))
}
#[test]
fn waits_during_grace() {
let t0 = Instant::now();
let mut l = ladder(t0);
assert_eq!(
l.poll(t0 + Duration::from_secs(1), false, false),
LadderAction::Wait
);
}
#[test]
fn escalates_term_then_kill() {
let t0 = Instant::now();
let mut l = ladder(t0);
assert_eq!(
l.poll(t0 + Duration::from_secs(5), false, false),
LadderAction::Term
);
assert_eq!(
l.poll(t0 + Duration::from_secs(6), false, false),
LadderAction::Wait
);
assert_eq!(
l.poll(t0 + Duration::from_secs(7), false, false),
LadderAction::Kill
);
}
#[test]
fn all_exited_is_done() {
let t0 = Instant::now();
let mut l = ladder(t0);
assert_eq!(
l.poll(t0 + Duration::from_secs(1), true, false),
LadderAction::Done
);
}
#[test]
fn force_collapses_to_kill() {
let t0 = Instant::now();
let mut l = ladder(t0);
assert_eq!(l.poll(t0, false, true), LadderAction::Kill);
assert_eq!(
l.poll(t0 + Duration::from_millis(1), false, true),
LadderAction::Wait
);
}
#[test]
fn done_after_kill_when_reaped() {
let t0 = Instant::now();
let mut l = ladder(t0);
l.poll(t0, false, true); assert_eq!(
l.poll(t0 + Duration::from_secs(1), true, false),
LadderAction::Done
);
}
}