rightkit_daemon/
shutdown.rs1use std::sync::mpsc;
15use std::sync::{Arc, Condvar, Mutex};
16use std::time::{Duration, Instant};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum PhaseOutcome {
21 Completed,
22 Failed(String),
24 TimedOut,
26 Panicked,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct PhaseReport {
33 pub name: String,
34 pub outcome: PhaseOutcome,
35 pub elapsed: Duration,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Default)]
40pub struct ShutdownReport {
41 pub phases: Vec<PhaseReport>,
42}
43
44impl ShutdownReport {
45 pub fn ok(&self) -> bool {
47 self.phases
48 .iter()
49 .all(|p| p.outcome == PhaseOutcome::Completed)
50 }
51}
52
53type PhaseFn = Box<dyn FnOnce() -> Result<(), String> + Send>;
54type ExitFn = Arc<dyn Fn(i32) + Send + Sync>;
55
56struct Phase {
57 name: String,
58 deadline: Duration,
59 run: PhaseFn,
60}
61
62struct Inner {
63 phases: Mutex<Option<Vec<Phase>>>,
64 watchdog: Mutex<Option<(Duration, i32)>>,
65 exit: Mutex<ExitFn>,
66 report: Mutex<Option<ShutdownReport>>,
67 done: Condvar,
68}
69
70#[derive(Clone)]
72pub struct OrderedShutdown {
73 inner: Arc<Inner>,
74}
75
76impl Default for OrderedShutdown {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82impl OrderedShutdown {
83 pub fn new() -> Self {
84 Self {
85 inner: Arc::new(Inner {
86 phases: Mutex::new(Some(Vec::new())),
87 watchdog: Mutex::new(None),
88 exit: Mutex::new(Arc::new(|code| std::process::exit(code))),
89 report: Mutex::new(None),
90 done: Condvar::new(),
91 }),
92 }
93 }
94
95 #[must_use]
98 pub fn phase(
99 self,
100 name: impl Into<String>,
101 deadline: Duration,
102 run: impl FnOnce() -> Result<(), String> + Send + 'static,
103 ) -> Self {
104 if let Some(phases) = self
105 .inner
106 .phases
107 .lock()
108 .unwrap_or_else(|e| e.into_inner())
109 .as_mut()
110 {
111 phases.push(Phase {
112 name: name.into(),
113 deadline: deadline.max(Duration::from_millis(1)),
114 run: Box::new(run),
115 });
116 }
117 self
118 }
119
120 #[must_use]
123 pub fn watchdog(self, hard_deadline: Duration, exit_code: i32) -> Self {
124 *self
125 .inner
126 .watchdog
127 .lock()
128 .unwrap_or_else(|e| e.into_inner()) = Some((hard_deadline, exit_code));
129 self
130 }
131
132 #[must_use]
135 pub fn on_watchdog(self, exit: impl Fn(i32) + Send + Sync + 'static) -> Self {
136 *self.inner.exit.lock().unwrap_or_else(|e| e.into_inner()) = Arc::new(exit);
137 self
138 }
139
140 pub fn run(&self) -> ShutdownReport {
142 let phases = self
143 .inner
144 .phases
145 .lock()
146 .unwrap_or_else(|e| e.into_inner())
147 .take();
148 let Some(phases) = phases else {
149 let mut report = self.inner.report.lock().unwrap_or_else(|e| e.into_inner());
150 while report.is_none() {
151 report = self
152 .inner
153 .done
154 .wait(report)
155 .unwrap_or_else(|e| e.into_inner());
156 }
157 return report.clone().unwrap_or_default();
158 };
159
160 let finished = Arc::new((Mutex::new(false), Condvar::new()));
161 let watchdog = *self
162 .inner
163 .watchdog
164 .lock()
165 .unwrap_or_else(|e| e.into_inner());
166 if let Some((hard, code)) = watchdog {
167 let finished = finished.clone();
168 let exit = self
169 .inner
170 .exit
171 .lock()
172 .unwrap_or_else(|e| e.into_inner())
173 .clone();
174 let _ = std::thread::Builder::new()
175 .name("rk-shutdown-watchdog".into())
176 .spawn(move || {
177 let (flag, cv) = &*finished;
178 let guard = flag.lock().unwrap_or_else(|e| e.into_inner());
179 let (guard, _) = cv
180 .wait_timeout_while(guard, hard, |done| !*done)
181 .unwrap_or_else(|e| e.into_inner());
182 if !*guard {
183 exit(code);
184 }
185 });
186 }
187
188 let mut report = ShutdownReport::default();
189 for phase in phases {
190 let started = Instant::now();
191 let (tx, rx) = mpsc::channel();
192 let run = phase.run;
193 let spawned = std::thread::Builder::new()
194 .name(format!("rk-shutdown-{}", phase.name))
195 .spawn(move || {
196 let outcome = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(run))
197 {
198 Ok(Ok(())) => PhaseOutcome::Completed,
199 Ok(Err(e)) => PhaseOutcome::Failed(e),
200 Err(_) => PhaseOutcome::Panicked,
201 };
202 let _ = tx.send(outcome);
203 });
204 let outcome = match spawned {
205 Err(e) => PhaseOutcome::Failed(format!("could not start the phase: {e}")),
206 Ok(_) => rx
207 .recv_timeout(phase.deadline)
208 .unwrap_or(PhaseOutcome::TimedOut),
209 };
210 report.phases.push(PhaseReport {
211 name: phase.name,
212 outcome,
213 elapsed: started.elapsed(),
214 });
215 }
216
217 {
218 let (flag, cv) = &*finished;
219 *flag.lock().unwrap_or_else(|e| e.into_inner()) = true;
220 cv.notify_all();
221 }
222 *self.inner.report.lock().unwrap_or_else(|e| e.into_inner()) = Some(report.clone());
223 self.inner.done.notify_all();
224 report
225 }
226}