use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::PoisonError;
static WARNED: AtomicBool = AtomicBool::new(false);
fn warn_once() {
if !WARNED.swap(true, Ordering::Relaxed) {
tracing::warn!(
target: "bamboo::lock_poison",
"recovered from a poisoned std lock (a thread panicked while holding it); \
continuing with the last-good data. Further recoveries this process are silenced."
);
}
}
pub trait PoisonRecover<G> {
fn recover_poison(self) -> G;
}
impl<G> PoisonRecover<G> for Result<G, PoisonError<G>> {
fn recover_poison(self) -> G {
match self {
Ok(guard) => guard,
Err(poisoned) => {
warn_once();
poisoned.into_inner()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::{Mutex, RwLock};
#[test]
fn recover_poison_returns_data_after_a_mutex_is_poisoned() {
let lock = Mutex::new(vec![1, 2, 3]);
let poisoned = catch_unwind(AssertUnwindSafe(|| {
let _guard = lock.lock().unwrap();
panic!("intentional poison for test");
}));
assert!(poisoned.is_err(), "setup panic should have been caught");
assert!(lock.lock().is_err(), "the mutex should now be poisoned");
assert_eq!(&*lock.lock().recover_poison(), &[1, 2, 3]);
}
#[test]
fn recover_poison_returns_data_after_an_rwlock_is_poisoned() {
let lock = RwLock::new(7u32);
let poisoned = catch_unwind(AssertUnwindSafe(|| {
let _guard = lock.write().unwrap();
panic!("intentional poison for test");
}));
assert!(poisoned.is_err());
assert_eq!(*lock.read().recover_poison(), 7);
*lock.write().recover_poison() = 9;
assert_eq!(*lock.read().recover_poison(), 9);
}
}