use std::sync::{Condvar, Mutex};
use std::time::Duration;
#[derive(Debug, Default)]
pub struct Shutdown {
reason: Mutex<Option<String>>,
changed: Condvar,
}
impl Shutdown {
pub fn request(&self, reason: impl Into<String>) {
let mut slot = self.reason.lock().unwrap();
if slot.is_none() {
*slot = Some(reason.into());
self.changed.notify_all();
}
}
#[allow(dead_code)] pub fn is_requested(&self) -> bool {
self.reason.lock().unwrap().is_some()
}
#[allow(dead_code)] pub fn reason(&self) -> Option<String> {
self.reason.lock().unwrap().clone()
}
pub fn wait_timeout(&self, timeout: Duration) -> Option<String> {
let slot = self.reason.lock().unwrap();
let (slot, _) = self
.changed
.wait_timeout_while(slot, timeout, |reason| reason.is_none())
.unwrap();
slot.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn starts_quiet() {
let shutdown = Shutdown::default();
assert!(!shutdown.is_requested());
assert_eq!(shutdown.reason(), None);
assert_eq!(shutdown.wait_timeout(Duration::from_millis(1)), None);
}
#[test]
fn keeps_the_first_reason() {
let shutdown = Shutdown::default();
shutdown.request("device gone");
shutdown.request("something else");
assert!(shutdown.is_requested());
assert_eq!(shutdown.reason().as_deref(), Some("device gone"));
}
#[test]
fn wakes_a_waiter() {
let shutdown = Arc::new(Shutdown::default());
let signaller = Arc::clone(&shutdown);
std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(20));
signaller.request("device gone");
});
assert_eq!(
shutdown.wait_timeout(Duration::from_secs(5)).as_deref(),
Some("device gone")
);
}
}