1use std::sync::atomic::{AtomicBool, Ordering};
24use std::sync::Arc;
25use std::time::Duration;
26
27use tokio::sync::watch;
28use tokio::time::timeout;
29use tracing::{info, warn};
30
31#[derive(Debug, Clone)]
39pub struct ShutdownController {
40 inner: Arc<ShutdownInner>,
41}
42
43#[derive(Debug)]
44struct ShutdownInner {
45 initiated: AtomicBool,
47 tx: watch::Sender<bool>,
49 rx: watch::Receiver<bool>,
51 active_ops: AtomicCounter,
53}
54
55#[derive(Debug, Default)]
57struct AtomicCounter {
58 count: std::sync::atomic::AtomicUsize,
59}
60
61impl AtomicCounter {
62 fn increment(&self) -> usize {
63 self.count.fetch_add(1, Ordering::SeqCst) + 1
64 }
65
66 fn decrement(&self) -> usize {
67 self.count.fetch_sub(1, Ordering::SeqCst) - 1
68 }
69
70 fn get(&self) -> usize {
71 self.count.load(Ordering::SeqCst)
72 }
73}
74
75impl Default for ShutdownController {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl ShutdownController {
82 pub fn new() -> Self {
84 let (tx, rx) = watch::channel(false);
85 Self {
86 inner: Arc::new(ShutdownInner {
87 initiated: AtomicBool::new(false),
88 tx,
89 rx,
90 active_ops: AtomicCounter::default(),
91 }),
92 }
93 }
94
95 pub fn subscribe(&self) -> watch::Receiver<bool> {
99 self.inner.rx.clone()
100 }
101
102 pub fn is_shutdown(&self) -> bool {
104 self.inner.initiated.load(Ordering::SeqCst)
105 }
106
107 pub fn shutdown_signal(&self) -> ShutdownSignal {
109 ShutdownSignal {
110 rx: self.inner.rx.clone(),
111 }
112 }
113
114 pub async fn shutdown(&self, grace_period: Duration) -> bool {
123 if self
125 .inner
126 .initiated
127 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
128 .is_err()
129 {
130 return true;
132 }
133
134 info!("initiating graceful shutdown with {:?} grace period", grace_period);
135
136 let _ = self.inner.tx.send(true);
138
139 let result = timeout(grace_period, self.wait_for_completion()).await;
141
142 match result {
143 Ok(()) => {
144 info!("graceful shutdown completed");
145 true
146 }
147 Err(_) => {
148 let remaining = self.inner.active_ops.get();
149 warn!(
150 remaining_ops = remaining,
151 "graceful shutdown timed out, forcing shutdown"
152 );
153 false
154 }
155 }
156 }
157
158 async fn wait_for_completion(&self) {
160 loop {
161 if self.inner.active_ops.get() == 0 {
162 break;
163 }
164 tokio::time::sleep(Duration::from_millis(100)).await;
165 }
166 }
167
168 pub fn register_operation(&self) -> OperationGuard {
172 self.inner.active_ops.increment();
173 OperationGuard {
174 controller: self.clone(),
175 }
176 }
177
178 pub fn active_operations(&self) -> usize {
180 self.inner.active_ops.get()
181 }
182}
183
184#[derive(Debug)]
188pub struct OperationGuard {
189 controller: ShutdownController,
190}
191
192impl Drop for OperationGuard {
193 fn drop(&mut self) {
194 self.controller.inner.active_ops.decrement();
195 }
196}
197
198#[derive(Debug, Clone)]
200pub struct ShutdownSignal {
201 rx: watch::Receiver<bool>,
202}
203
204impl ShutdownSignal {
205 pub async fn wait(mut self) {
207 loop {
208 if *self.rx.borrow() {
209 return;
210 }
211 if self.rx.changed().await.is_err() {
212 return;
214 }
215 if *self.rx.borrow() {
216 return;
217 }
218 }
219 }
220}
221
222#[allow(clippy::expect_used)]
231pub async fn wait_for_signal() {
232 #[cfg(unix)]
233 {
234 use tokio::signal::unix::{signal, SignalKind};
235
236 let mut sigterm = signal(SignalKind::terminate()).expect("failed to install SIGTERM handler");
237 let mut sigint = signal(SignalKind::interrupt()).expect("failed to install SIGINT handler");
238
239 tokio::select! {
240 _ = sigterm.recv() => {
241 info!("received SIGTERM");
242 }
243 _ = sigint.recv() => {
244 info!("received SIGINT");
245 }
246 }
247 }
248
249 #[cfg(not(unix))]
250 {
251 tokio::signal::ctrl_c()
252 .await
253 .expect("failed to install Ctrl+C handler");
254 info!("received Ctrl+C");
255 }
256}
257
258#[derive(Debug, Clone)]
260pub struct ShutdownConfig {
261 pub grace_period: Duration,
263 pub listen_for_signals: bool,
265}
266
267impl Default for ShutdownConfig {
268 fn default() -> Self {
269 Self {
270 grace_period: Duration::from_secs(30),
271 listen_for_signals: true,
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn shutdown_controller_creation() {
282 let controller = ShutdownController::new();
283 assert!(!controller.is_shutdown());
284 assert_eq!(controller.active_operations(), 0);
285 }
286
287 #[test]
288 fn operation_tracking() {
289 let controller = ShutdownController::new();
290
291 {
292 let _guard1 = controller.register_operation();
293 assert_eq!(controller.active_operations(), 1);
294
295 let _guard2 = controller.register_operation();
296 assert_eq!(controller.active_operations(), 2);
297 }
298
299 assert_eq!(controller.active_operations(), 0);
300 }
301
302 #[tokio::test]
303 async fn shutdown_signal() {
304 let controller = ShutdownController::new();
305 let mut rx = controller.subscribe();
306
307 let controller_clone = controller.clone();
309 tokio::spawn(async move {
310 tokio::time::sleep(Duration::from_millis(50)).await;
311 controller_clone.shutdown(Duration::from_millis(100)).await;
312 });
313
314 rx.changed().await.expect("should receive shutdown signal");
316 assert!(controller.is_shutdown());
317 }
318
319 #[tokio::test]
320 async fn shutdown_waits_for_operations() {
321 let controller = ShutdownController::new();
322
323 let guard = controller.register_operation();
325 assert_eq!(controller.active_operations(), 1);
326
327 let controller_clone = controller.clone();
329 let handle = tokio::spawn(async move {
330 controller_clone.shutdown(Duration::from_secs(5)).await
331 });
332
333 tokio::time::sleep(Duration::from_millis(100)).await;
335 drop(guard);
336
337 let result = handle.await.expect("shutdown task should complete");
339 assert!(result);
340 }
341
342 #[test]
343 fn shutdown_config_defaults() {
344 let config = ShutdownConfig::default();
345 assert_eq!(config.grace_period, Duration::from_secs(30));
346 assert!(config.listen_for_signals);
347 }
348}