use std::sync::{LockResult, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[inline]
pub fn recover<Guard>(result: LockResult<Guard>) -> Guard {
result.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[inline]
pub fn lock_or_recover<T: ?Sized>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
recover(mutex.lock())
}
#[inline]
pub fn read_or_recover<T: ?Sized>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
recover(lock.read())
}
#[inline]
pub fn write_or_recover<T: ?Sized>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
recover(lock.write())
}
pub trait MutexExt<T: ?Sized> {
fn lock_or_recover(&self) -> MutexGuard<'_, T>;
}
impl<T: ?Sized> MutexExt<T> for Mutex<T> {
#[inline]
fn lock_or_recover(&self) -> MutexGuard<'_, T> {
lock_or_recover(self)
}
}
pub trait RwLockExt<T: ?Sized> {
fn read_or_recover(&self) -> RwLockReadGuard<'_, T>;
fn write_or_recover(&self) -> RwLockWriteGuard<'_, T>;
}
impl<T: ?Sized> RwLockExt<T> for RwLock<T> {
#[inline]
fn read_or_recover(&self) -> RwLockReadGuard<'_, T> {
read_or_recover(self)
}
#[inline]
fn write_or_recover(&self) -> RwLockWriteGuard<'_, T> {
write_or_recover(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn mutex_recovers_via_free_function_and_ext_trait() {
let mutex = Arc::new(Mutex::new(vec![1, 2, 3]));
let poisoner = Arc::clone(&mutex);
let joined = std::thread::spawn(move || {
let _guard = poisoner.lock().expect("thread must acquire the lock first");
panic!("intentional poison for test");
})
.join();
assert!(joined.is_err(), "poisoning thread should have panicked");
assert!(
mutex.lock().is_err(),
"mutex should be poisoned after the panic"
);
let guard = lock_or_recover(&mutex);
assert_eq!(*guard, vec![1, 2, 3]);
drop(guard);
let guard = mutex.lock_or_recover();
assert_eq!(*guard, vec![1, 2, 3]);
}
#[test]
fn rwlock_recovers_read_and_write_via_free_function_and_ext_trait() {
let lock = Arc::new(RwLock::new(10_i32));
let poisoner = Arc::clone(&lock);
let joined = std::thread::spawn(move || {
let _guard = poisoner
.write()
.expect("thread must acquire the lock first");
panic!("intentional poison for test");
})
.join();
assert!(joined.is_err(), "poisoning thread should have panicked");
assert!(lock.read().is_err(), "rwlock should be poisoned");
assert_eq!(*read_or_recover(&lock), 10);
*write_or_recover(&lock) += 5;
assert_eq!(*read_or_recover(&lock), 15);
assert_eq!(*lock.read_or_recover(), 15);
*lock.write_or_recover() += 1;
assert_eq!(*lock.read_or_recover(), 16);
}
}