use std::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[derive(Debug, thiserror::Error)]
pub enum LockRecoveryError {
#[error("Lock was poisoned: {0}")]
Poisoned(String),
#[error("Lock acquisition timed out")]
Timeout,
}
pub fn recover_rwlock_read<T>(
lock: &RwLock<T>,
) -> Result<RwLockReadGuard<'_, T>, LockRecoveryError> {
match lock.read() {
Ok(guard) => Ok(guard),
Err(poison_err) => {
tracing::warn!(
"RwLock read guard recovered from poisoned state. \
Data may be inconsistent."
);
Ok(poison_err.into_inner())
}
}
}
pub fn recover_rwlock_write<T>(
lock: &RwLock<T>,
) -> Result<RwLockWriteGuard<'_, T>, LockRecoveryError> {
match lock.write() {
Ok(guard) => Ok(guard),
Err(poison_err) => {
tracing::warn!(
"RwLock write guard recovered from poisoned state. \
Data may be inconsistent."
);
Ok(poison_err.into_inner())
}
}
}
pub fn recover_mutex<T>(lock: &Mutex<T>) -> Result<MutexGuard<'_, T>, LockRecoveryError> {
match lock.lock() {
Ok(guard) => Ok(guard),
Err(poison_err) => {
tracing::warn!(
"Mutex guard recovered from poisoned state. \
Data may be inconsistent."
);
Ok(poison_err.into_inner())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use std::sync::Arc;
use std::thread;
#[rstest]
fn test_recover_rwlock_read_normal() {
let lock = RwLock::new(42);
let guard = recover_rwlock_read(&lock);
assert!(guard.is_ok());
assert_eq!(*guard.unwrap(), 42);
}
#[rstest]
fn test_recover_rwlock_write_normal() {
let lock = RwLock::new(42);
let guard = recover_rwlock_write(&lock);
assert!(guard.is_ok());
assert_eq!(*guard.unwrap(), 42);
}
#[rstest]
fn test_recover_mutex_normal() {
let lock = Mutex::new(42);
let guard = recover_mutex(&lock);
assert!(guard.is_ok());
assert_eq!(*guard.unwrap(), 42);
}
#[rstest]
fn test_recover_mutex_from_poisoned() {
let lock = Arc::new(Mutex::new(42));
let lock_clone = Arc::clone(&lock);
let _ = thread::spawn(move || {
let _guard = lock_clone.lock().unwrap();
panic!("intentional panic to poison the lock");
})
.join();
assert!(lock.lock().is_err(), "Lock should be poisoned");
let result = recover_mutex(&lock);
assert!(result.is_ok(), "Should recover from poisoned mutex");
assert_eq!(*result.unwrap(), 42);
}
#[rstest]
fn test_recover_rwlock_read_from_poisoned() {
let lock = Arc::new(RwLock::new(42));
let lock_clone = Arc::clone(&lock);
let _ = thread::spawn(move || {
let _guard = lock_clone.write().unwrap();
panic!("intentional panic to poison the lock");
})
.join();
assert!(lock.read().is_err(), "Lock should be poisoned");
let result = recover_rwlock_read(&lock);
assert!(result.is_ok(), "Should recover from poisoned RwLock");
assert_eq!(*result.unwrap(), 42);
}
#[rstest]
fn test_recover_rwlock_write_from_poisoned() {
let lock = Arc::new(RwLock::new(42));
let lock_clone = Arc::clone(&lock);
let _ = thread::spawn(move || {
let _guard = lock_clone.write().unwrap();
panic!("intentional panic to poison the lock");
})
.join();
assert!(lock.write().is_err(), "Lock should be poisoned");
let result = recover_rwlock_write(&lock);
assert!(result.is_ok(), "Should recover from poisoned RwLock");
assert_eq!(*result.unwrap(), 42);
}
}