agentd/supervisor/
kill.rs1use std::time::{Duration, Instant};
18
19pub const DEFAULT_GRACE: Duration = Duration::from_secs(5);
21pub const DEFAULT_KILL_GRACE: Duration = Duration::from_secs(2);
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum LadderAction {
27 Wait,
29 Term,
31 Kill,
33 Done,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
38enum Phase {
39 Cancel,
40 Term,
41 Kill,
42 Done,
43}
44
45#[derive(Debug)]
49pub struct Ladder {
50 started: Instant,
51 grace: Duration,
52 kill_grace: Duration,
53 phase: Phase,
54}
55
56impl Ladder {
57 pub fn new(now: Instant, grace: Duration, kill_grace: Duration) -> Ladder {
60 Ladder {
61 started: now,
62 grace,
63 kill_grace,
64 phase: Phase::Cancel,
65 }
66 }
67
68 pub fn with_defaults(now: Instant) -> Ladder {
69 Ladder::new(now, DEFAULT_GRACE, DEFAULT_KILL_GRACE)
70 }
71
72 pub fn poll(&mut self, now: Instant, all_exited: bool, force: bool) -> LadderAction {
75 if all_exited {
76 self.phase = Phase::Done;
77 return LadderAction::Done;
78 }
79 if force && self.phase < Phase::Kill {
80 self.phase = Phase::Kill;
81 return LadderAction::Kill;
82 }
83 let elapsed = now.saturating_duration_since(self.started);
84 match self.phase {
85 Phase::Cancel if elapsed >= self.grace => {
86 self.phase = Phase::Term;
87 LadderAction::Term
88 }
89 Phase::Term if elapsed >= self.grace + self.kill_grace => {
90 self.phase = Phase::Kill;
91 LadderAction::Kill
92 }
93 Phase::Done => LadderAction::Done,
94 _ => LadderAction::Wait,
98 }
99 }
100}
101
102#[cfg(unix)]
105pub fn signal_group(pgid: i32, sig: i32) {
106 if pgid > 1 {
107 unsafe {
108 libc::killpg(pgid, sig);
109 }
110 }
111}
112
113#[cfg(unix)]
114pub fn term_group(pgid: i32) {
115 signal_group(pgid, libc::SIGTERM);
116}
117
118#[cfg(unix)]
119pub fn kill_group(pgid: i32) {
120 signal_group(pgid, libc::SIGKILL);
121}
122
123#[cfg(not(unix))]
124pub fn signal_group(_pgid: i32, _sig: i32) {}
125#[cfg(not(unix))]
126pub fn term_group(_pgid: i32) {}
127#[cfg(not(unix))]
128pub fn kill_group(_pgid: i32) {}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 fn ladder(now: Instant) -> Ladder {
135 Ladder::new(now, Duration::from_secs(5), Duration::from_secs(2))
136 }
137
138 #[test]
139 fn waits_during_grace() {
140 let t0 = Instant::now();
141 let mut l = ladder(t0);
142 assert_eq!(
143 l.poll(t0 + Duration::from_secs(1), false, false),
144 LadderAction::Wait
145 );
146 }
147
148 #[test]
149 fn escalates_term_then_kill() {
150 let t0 = Instant::now();
151 let mut l = ladder(t0);
152 assert_eq!(
153 l.poll(t0 + Duration::from_secs(5), false, false),
154 LadderAction::Term
155 );
156 assert_eq!(
158 l.poll(t0 + Duration::from_secs(6), false, false),
159 LadderAction::Wait
160 );
161 assert_eq!(
163 l.poll(t0 + Duration::from_secs(7), false, false),
164 LadderAction::Kill
165 );
166 }
167
168 #[test]
169 fn all_exited_is_done() {
170 let t0 = Instant::now();
171 let mut l = ladder(t0);
172 assert_eq!(
173 l.poll(t0 + Duration::from_secs(1), true, false),
174 LadderAction::Done
175 );
176 }
177
178 #[test]
179 fn force_collapses_to_kill() {
180 let t0 = Instant::now();
181 let mut l = ladder(t0);
182 assert_eq!(l.poll(t0, false, true), LadderAction::Kill);
184 assert_eq!(
186 l.poll(t0 + Duration::from_millis(1), false, true),
187 LadderAction::Wait
188 );
189 }
190
191 #[test]
192 fn done_after_kill_when_reaped() {
193 let t0 = Instant::now();
194 let mut l = ladder(t0);
195 l.poll(t0, false, true); assert_eq!(
197 l.poll(t0 + Duration::from_secs(1), true, false),
198 LadderAction::Done
199 );
200 }
201}