1use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
6use std::time::{Duration, Instant};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ShutdownResult {
15 Clean,
17 Timeout {
19 remaining: usize,
21 },
22}
23
24pub struct GracefulShutdown {
46 shutdown_requested: AtomicBool,
48 active_count: AtomicUsize,
50 timeout: Duration,
52}
53
54impl GracefulShutdown {
55 pub fn new(timeout: Duration) -> Self {
57 Self {
58 shutdown_requested: AtomicBool::new(false),
59 active_count: AtomicUsize::new(0),
60 timeout,
61 }
62 }
63
64 #[must_use]
66 pub fn is_shutdown_requested(&self) -> bool {
67 self.shutdown_requested.load(Ordering::Acquire)
68 }
69
70 #[must_use]
72 pub fn active_count(&self) -> usize {
73 self.active_count.load(Ordering::Acquire)
74 }
75
76 pub fn register(&self) -> Option<ShutdownGuard<'_>> {
80 if self.is_shutdown_requested() {
81 return None; }
83 self.active_count.fetch_add(1, Ordering::AcqRel);
84 Some(ShutdownGuard { shutdown: self })
85 }
86
87 pub fn shutdown(&self) -> ShutdownResult {
94 self.shutdown_requested.store(true, Ordering::Release);
96
97 let deadline = Instant::now() + self.timeout;
99
100 loop {
101 let active = self.active_count.load(Ordering::Acquire);
102 if active == 0 {
103 return ShutdownResult::Clean;
104 }
105 if Instant::now() >= deadline {
106 return ShutdownResult::Timeout { remaining: active };
107 }
108 std::thread::sleep(Duration::from_millis(10));
109 }
110 }
111
112 pub fn reset(&self) {
114 self.shutdown_requested.store(false, Ordering::Release);
115 }
117}
118
119impl Default for GracefulShutdown {
120 fn default() -> Self {
121 Self::new(Duration::from_secs(30))
122 }
123}
124
125pub struct ShutdownGuard<'a> {
127 shutdown: &'a GracefulShutdown,
128}
129
130impl Drop for ShutdownGuard<'_> {
131 fn drop(&mut self) {
132 self.shutdown.active_count.fetch_sub(1, Ordering::AcqRel);
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_shutdown_result_eq() {
142 assert_eq!(ShutdownResult::Clean, ShutdownResult::Clean);
143 assert_eq!(
144 ShutdownResult::Timeout { remaining: 5 },
145 ShutdownResult::Timeout { remaining: 5 }
146 );
147 assert_ne!(ShutdownResult::Clean, ShutdownResult::Timeout { remaining: 0 });
148 }
149
150 #[test]
151 fn test_graceful_shutdown_new() {
152 let shutdown = GracefulShutdown::new(Duration::from_secs(5));
153 assert!(!shutdown.is_shutdown_requested());
154 assert_eq!(shutdown.active_count(), 0);
155 }
156
157 #[test]
158 fn test_graceful_shutdown_default() {
159 let shutdown = GracefulShutdown::default();
160 assert!(!shutdown.is_shutdown_requested());
161 assert_eq!(shutdown.timeout, Duration::from_secs(30));
162 }
163
164 #[test]
165 fn test_graceful_shutdown_register() {
166 let shutdown = GracefulShutdown::new(Duration::from_secs(5));
167
168 let guard1 = shutdown.register();
169 assert!(guard1.is_some());
170 assert_eq!(shutdown.active_count(), 1);
171
172 let guard2 = shutdown.register();
173 assert!(guard2.is_some());
174 assert_eq!(shutdown.active_count(), 2);
175
176 drop(guard1);
177 assert_eq!(shutdown.active_count(), 1);
178
179 drop(guard2);
180 assert_eq!(shutdown.active_count(), 0);
181 }
182
183 #[test]
184 fn test_graceful_shutdown_clean() {
185 let shutdown = GracefulShutdown::new(Duration::from_millis(100));
186
187 let guard = shutdown.register();
189 drop(guard);
190
191 let result = shutdown.shutdown();
192 assert_eq!(result, ShutdownResult::Clean);
193 }
194
195 #[test]
196 fn test_graceful_shutdown_rejects_after_shutdown() {
197 let shutdown = GracefulShutdown::new(Duration::from_millis(10));
198
199 let result = shutdown.shutdown();
201 assert_eq!(result, ShutdownResult::Clean);
202
203 let guard = shutdown.register();
205 assert!(guard.is_none(), "Should reject new operations after shutdown");
206 }
207
208 #[test]
209 fn test_graceful_shutdown_reset() {
210 let shutdown = GracefulShutdown::new(Duration::from_millis(10));
211
212 shutdown.shutdown();
214 assert!(shutdown.is_shutdown_requested());
215
216 shutdown.reset();
218 assert!(!shutdown.is_shutdown_requested());
219
220 let guard = shutdown.register();
222 assert!(guard.is_some());
223 }
224
225 #[test]
230 fn test_falsify_shutdown_timeout() {
231 let shutdown = GracefulShutdown::new(Duration::from_millis(50));
232
233 let _guard = shutdown.register();
235 assert_eq!(shutdown.active_count(), 1);
236
237 let start = Instant::now();
239 let result = shutdown.shutdown();
240 let elapsed = start.elapsed();
241
242 match result {
244 ShutdownResult::Timeout { remaining } => {
245 assert_eq!(remaining, 1, "Should report exactly 1 remaining operation");
246 }
247 ShutdownResult::Clean => {
248 panic!("FALSIFICATION FAILED: Shutdown reported Clean with active operation");
249 }
250 }
251
252 assert!(
254 elapsed >= Duration::from_millis(40),
255 "FALSIFICATION FAILED: Shutdown returned too early ({:?} < 40ms)",
256 elapsed
257 );
258 }
259
260 #[test]
265 fn test_falsify_guard_drop_semantics() {
266 let shutdown = GracefulShutdown::new(Duration::from_secs(1));
267
268 let mut guards: Vec<_> = (0..100).filter_map(|_| shutdown.register()).collect();
270 assert_eq!(shutdown.active_count(), 100, "FALSIFICATION FAILED: Not all guards registered");
271
272 guards.truncate(50);
274
275 assert_eq!(
278 shutdown.active_count(),
279 50,
280 "FALSIFICATION FAILED: Guard drop did not correctly decrement count"
281 );
282
283 drop(guards);
285 assert_eq!(
286 shutdown.active_count(),
287 0,
288 "FALSIFICATION FAILED: Final drop did not clear all guards"
289 );
290 }
291
292 #[test]
297 fn test_falsify_rejection_after_shutdown_flag() {
298 let shutdown = GracefulShutdown::new(Duration::from_secs(5));
299
300 shutdown.shutdown_requested.store(true, Ordering::Release);
302
303 let mut accepted = 0;
305 for _ in 0..100 {
306 if shutdown.register().is_some() {
307 accepted += 1;
308 }
309 }
310
311 assert_eq!(
313 accepted, 0,
314 "FALSIFICATION FAILED: {} operations accepted after shutdown flag set",
315 accepted
316 );
317 }
318}