use std::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
pub trait MutexExt<T> {
fn lock_or_recover(&self) -> MutexGuard<'_, T>;
}
impl<T> MutexExt<T> for Mutex<T> {
#[track_caller]
fn lock_or_recover(&self) -> MutexGuard<'_, T> {
let location = std::panic::Location::caller();
self.lock().unwrap_or_else(|e| {
tracing::warn!(location = %location, "Mutex poisoned — recovering with inner value");
e.into_inner()
})
}
}
pub trait RwLockExt<T> {
fn read_or_recover(&self) -> RwLockReadGuard<'_, T>;
fn write_or_recover(&self) -> RwLockWriteGuard<'_, T>;
}
impl<T> RwLockExt<T> for RwLock<T> {
#[track_caller]
fn read_or_recover(&self) -> RwLockReadGuard<'_, T> {
let location = std::panic::Location::caller();
self.read().unwrap_or_else(|e| {
tracing::warn!(location = %location, "RwLock poisoned — recovering with inner value");
e.into_inner()
})
}
#[track_caller]
fn write_or_recover(&self) -> RwLockWriteGuard<'_, T> {
let location = std::panic::Location::caller();
self.write().unwrap_or_else(|e| {
tracing::warn!(location = %location, "RwLock poisoned — recovering with inner value");
e.into_inner()
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::thread;
fn poison_mutex(lock: &Arc<Mutex<Vec<u32>>>) {
let cloned = Arc::clone(lock);
let _ = thread::spawn(move || {
let _guard = cloned.lock().unwrap();
panic!("poison the lock");
})
.join();
}
#[test]
fn mutex_recovers_after_poison() {
let lock = Arc::new(Mutex::new(vec![1u32]));
poison_mutex(&lock);
assert!(lock.is_poisoned());
let mut guard = lock.lock_or_recover();
guard.push(2);
assert_eq!(*guard, vec![1, 2]);
drop(guard);
assert_eq!(lock.lock_or_recover().len(), 2);
}
#[test]
fn mutex_unpoisoned_behaves_normally() {
let lock = Mutex::new(7u32);
assert_eq!(*lock.lock_or_recover(), 7);
}
#[test]
fn rwlock_recovers_after_poison() {
let lock = Arc::new(RwLock::new(vec![1u32]));
let cloned = Arc::clone(&lock);
let _ = thread::spawn(move || {
let _guard = cloned.write().unwrap();
panic!("poison the lock");
})
.join();
assert!(lock.is_poisoned());
lock.write_or_recover().push(2);
assert_eq!(*lock.read_or_recover(), vec![1, 2]);
}
}