use std::fmt;
pub struct KillOnDrop<F: FnOnce()> {
cleanup: Option<F>,
}
impl<F: FnOnce()> KillOnDrop<F> {
pub fn new(cleanup: F) -> Self {
Self {
cleanup: Some(cleanup),
}
}
pub fn defuse(mut self) {
self.cleanup = None;
}
}
impl<F: FnOnce()> Drop for KillOnDrop<F> {
fn drop(&mut self) {
if let Some(cleanup) = self.cleanup.take() {
cleanup();
}
}
}
impl<F: FnOnce()> fmt::Debug for KillOnDrop<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KillOnDrop")
.field("armed", &self.cleanup.is_some())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
#[test]
fn cleanup_runs_on_drop() {
let counter = Arc::new(AtomicU64::new(0));
let seen = Arc::clone(&counter);
{
let _guard = KillOnDrop::new(move || {
seen.fetch_add(1, Ordering::Release);
});
assert_eq!(counter.load(Ordering::Acquire), 0);
}
assert_eq!(counter.load(Ordering::Acquire), 1);
}
#[test]
fn defused_guard_skips_cleanup() {
let counter = Arc::new(AtomicU64::new(0));
let seen = Arc::clone(&counter);
{
let guard = KillOnDrop::new(move || {
seen.fetch_add(1, Ordering::Release);
});
guard.defuse();
}
assert_eq!(counter.load(Ordering::Acquire), 0);
}
#[test]
fn cleanup_runs_during_panic_unwind() {
let counter = Arc::new(AtomicU64::new(0));
let seen = Arc::clone(&counter);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
let _guard = KillOnDrop::new(|| {
seen.fetch_add(1, Ordering::Release);
});
panic!("simulated assertion failure");
}));
assert!(result.is_err());
assert_eq!(counter.load(Ordering::Acquire), 1);
}
}