use std::sync::{Mutex, MutexGuard};
pub trait LockExt<T> {
fn lock_or_reset(&self, reset: impl FnOnce(&mut T)) -> MutexGuard<'_, T>;
fn lock_or_default(&self) -> MutexGuard<'_, T>
where
T: Default,
{
self.lock_or_reset(|state| *state = T::default())
}
}
impl<T> LockExt<T> for Mutex<T> {
fn lock_or_reset(&self, reset: impl FnOnce(&mut T)) -> MutexGuard<'_, T> {
self.lock().unwrap_or_else(|poisoned| {
let mut state = poisoned.into_inner();
reset(&mut state);
self.clear_poison();
state
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn poison(m: &Mutex<Vec<u32>>) {
let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mut v = m.lock().unwrap();
v.push(2);
panic!("half-written");
}));
assert!(caught.is_err() && m.is_poisoned());
}
#[test]
fn a_poisoned_lock_resets_once_and_is_healthy_again() {
let m = Mutex::new(vec![1]);
assert_eq!(*m.lock_or_default(), [1]);
poison(&m);
assert!(m.lock_or_default().is_empty());
assert!(!m.is_poisoned());
m.lock_or_default().push(3);
assert_eq!(*m.lock_or_reset(|_| unreachable!("not poisoned")), [3]);
poison(&m);
assert_eq!(*m.lock_or_reset(|v| v.retain(|&x| x != 2)), [3]);
}
}