use std::sync::{Arc, Mutex};
pub struct ShutdownGuard {
inner: Arc<Mutex<Option<Box<dyn FnOnce() + Send>>>>,
}
impl ShutdownGuard {
pub fn new(on_drop: impl FnOnce() + Send + 'static) -> Self {
Self {
inner: Arc::new(Mutex::new(Some(Box::new(on_drop)))),
}
}
pub fn finish(self) {
self.fire();
std::mem::forget(self);
}
fn fire(&self) {
let cb = self.inner.lock().unwrap().take();
if let Some(cb) = cb {
cb();
}
}
}
impl Drop for ShutdownGuard {
fn drop(&mut self) {
self.fire();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn fires_on_drop() {
let calls = Arc::new(AtomicUsize::new(0));
let c = Arc::clone(&calls);
{
let _guard = ShutdownGuard::new(move || {
c.fetch_add(1, Ordering::SeqCst);
});
}
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn finish_fires_once_and_disarms() {
let calls = Arc::new(AtomicUsize::new(0));
let c = Arc::clone(&calls);
let guard = ShutdownGuard::new(move || {
c.fetch_add(1, Ordering::SeqCst);
});
guard.finish();
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn fires_exactly_once_despite_panic_path() {
let calls = Arc::new(AtomicUsize::new(0));
let c = Arc::clone(&calls);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _guard = ShutdownGuard::new(move || {
c.fetch_add(1, Ordering::SeqCst);
});
panic!("boom");
}));
assert!(result.is_err());
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
}