1use std::sync::{Condvar, Mutex};
10use std::time::Duration;
11
12#[derive(Debug, Default)]
14pub struct Shutdown {
15 reason: Mutex<Option<String>>,
16 changed: Condvar,
17}
18
19impl Shutdown {
20 pub fn request(&self, reason: impl Into<String>) {
23 let mut slot = self.reason.lock().unwrap();
24 if slot.is_none() {
25 *slot = Some(reason.into());
26 self.changed.notify_all();
27 }
28 }
29
30 #[allow(dead_code)] pub fn is_requested(&self) -> bool {
33 self.reason.lock().unwrap().is_some()
34 }
35
36 #[allow(dead_code)] pub fn reason(&self) -> Option<String> {
39 self.reason.lock().unwrap().clone()
40 }
41
42 pub fn wait_timeout(&self, timeout: Duration) -> Option<String> {
44 let slot = self.reason.lock().unwrap();
45 let (slot, _) = self
46 .changed
47 .wait_timeout_while(slot, timeout, |reason| reason.is_none())
48 .unwrap();
49 slot.clone()
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use std::sync::Arc;
57
58 #[test]
59 fn starts_quiet() {
60 let shutdown = Shutdown::default();
61 assert!(!shutdown.is_requested());
62 assert_eq!(shutdown.reason(), None);
63 assert_eq!(shutdown.wait_timeout(Duration::from_millis(1)), None);
64 }
65
66 #[test]
67 fn keeps_the_first_reason() {
68 let shutdown = Shutdown::default();
69 shutdown.request("device gone");
70 shutdown.request("something else");
71 assert!(shutdown.is_requested());
72 assert_eq!(shutdown.reason().as_deref(), Some("device gone"));
73 }
74
75 #[test]
76 fn wakes_a_waiter() {
77 let shutdown = Arc::new(Shutdown::default());
78 let signaller = Arc::clone(&shutdown);
79 std::thread::spawn(move || {
80 std::thread::sleep(Duration::from_millis(20));
81 signaller.request("device gone");
82 });
83 assert_eq!(
84 shutdown.wait_timeout(Duration::from_secs(5)).as_deref(),
85 Some("device gone")
86 );
87 }
88}