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