use std::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[inline]
pub fn lock_recover<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[inline]
#[allow(unused_variables)]
pub fn lock_recover_debug<'a, T>(mutex: &'a Mutex<T>, context: &str) -> MutexGuard<'a, T> {
mutex.lock().unwrap_or_else(|e| {
#[cfg(debug_assertions)]
eprintln!("[rich_rust::sync] mutex poison recovered at: {context}");
e.into_inner()
})
}
#[inline]
pub fn read_recover<T>(rwlock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
rwlock
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[inline]
pub fn write_recover<T>(rwlock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
rwlock
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(test)]
mod tests {
use super::*;
use std::panic::{self, AssertUnwindSafe};
use std::sync::Arc;
use std::thread;
#[test]
fn test_lock_recover_normal_operation() {
println!("[TEST] lock_recover with healthy mutex");
let mutex = Mutex::new(42);
let guard = lock_recover(&mutex);
println!("[TEST] Acquired lock, value = {}", *guard);
assert_eq!(*guard, 42);
println!("[TEST] PASS: lock_recover works on healthy mutex");
}
#[test]
fn test_lock_recover_after_poison() {
println!("[TEST] lock_recover after poisoning mutex");
let mutex = Mutex::new(42);
println!("[TEST] Step 1: Poisoning mutex by panicking while holding lock...");
let _ = panic::catch_unwind(AssertUnwindSafe(|| {
let _guard = mutex.lock().unwrap();
println!("[TEST] Inside panic scope, about to panic");
panic!("intentional panic to poison mutex");
}));
println!("[TEST] Step 2: Verifying mutex is actually poisoned...");
assert!(mutex.lock().is_err(), "Mutex should be poisoned");
println!("[TEST] Confirmed: mutex.lock() returns Err (poisoned)");
println!("[TEST] Step 3: Testing recovery...");
let guard = lock_recover(&mutex);
println!("[TEST] Recovery successful, value = {}", *guard);
assert_eq!(*guard, 42);
println!("[TEST] PASS: lock_recover recovers from poison");
}
#[test]
fn test_lock_recover_debug_context() {
println!("[TEST] lock_recover_debug with context");
let mutex = Mutex::new("hello");
let guard = lock_recover_debug(&mutex, "test_debug_context");
println!("[TEST] Value: {}", *guard);
assert_eq!(*guard, "hello");
println!("[TEST] PASS: lock_recover_debug works");
}
#[test]
fn test_lock_recover_debug_after_poison() {
println!("[TEST] lock_recover_debug after poison (should log in debug)");
let mutex = Mutex::new(99);
let _ = panic::catch_unwind(AssertUnwindSafe(|| {
let _guard = mutex.lock().unwrap();
panic!("intentional panic");
}));
let guard = lock_recover_debug(&mutex, "test_poison_debug");
assert_eq!(*guard, 99);
println!("[TEST] PASS: lock_recover_debug recovers with logging");
}
#[test]
fn test_read_recover_normal() {
println!("[TEST] read_recover with healthy RwLock");
let rwlock = RwLock::new(42);
let guard = read_recover(&rwlock);
println!("[TEST] Read value = {}", *guard);
assert_eq!(*guard, 42);
println!("[TEST] PASS: read_recover works on healthy RwLock");
}
#[test]
fn test_read_recover_after_write_poison() {
println!("[TEST] read_recover after write poison");
let rwlock = RwLock::new(42);
println!("[TEST] Poisoning via write lock...");
let _ = panic::catch_unwind(AssertUnwindSafe(|| {
let _guard = rwlock.write().unwrap();
panic!("intentional panic during write");
}));
println!("[TEST] Attempting read recovery...");
let guard = read_recover(&rwlock);
println!("[TEST] Read recovered, value = {}", *guard);
assert_eq!(*guard, 42);
println!("[TEST] PASS: read_recover works after write poison");
}
#[test]
fn test_write_recover_normal() {
println!("[TEST] write_recover with healthy RwLock");
let rwlock = RwLock::new(42);
{
let mut guard = write_recover(&rwlock);
*guard = 100;
println!("[TEST] Modified value to 100");
}
let guard = read_recover(&rwlock);
println!("[TEST] Verified new value = {}", *guard);
assert_eq!(*guard, 100);
println!("[TEST] PASS: write_recover works on healthy RwLock");
}
#[test]
fn test_write_recover_after_read_poison() {
println!("[TEST] write_recover after read poison (edge case)");
let rwlock = RwLock::new(42);
println!("[TEST] Poisoning via read lock...");
let _ = panic::catch_unwind(AssertUnwindSafe(|| {
let _guard = rwlock.read().unwrap();
panic!("intentional panic during read");
}));
println!("[TEST] Attempting write recovery...");
let mut guard = write_recover(&rwlock);
*guard = 200;
println!("[TEST] Write recovered, set value to 200");
drop(guard);
let read_guard = read_recover(&rwlock);
println!("[TEST] Verified new value = {}", *read_guard);
assert_eq!(*read_guard, 200);
println!("[TEST] PASS: write_recover works after read poison");
}
#[test]
fn test_multiple_recoveries_same_mutex() {
println!("[TEST] Multiple sequential recoveries on same mutex");
let mutex = Mutex::new(0);
for i in 1..=3 {
println!("[TEST] Iteration {i}: poisoning mutex...");
{
let mut guard = lock_recover(&mutex);
*guard = i;
println!("[TEST] Iteration {i}: set value to {}", *guard);
}
let _ = panic::catch_unwind(AssertUnwindSafe(|| {
let _guard = mutex.lock().unwrap();
println!("[TEST] Iteration {i}: about to panic");
panic!("intentional panic #{i}");
}));
println!("[TEST] Iteration {i}: recovering...");
let guard = lock_recover(&mutex);
println!("[TEST] Iteration {i}: recovered value = {}", *guard);
assert_eq!(*guard, i);
}
println!("[TEST] PASS: Multiple recoveries work correctly");
}
#[test]
fn test_concurrent_access_after_poison() {
println!("[TEST] Concurrent access after poison");
let mutex = Arc::new(Mutex::new(0));
{
let m = Arc::clone(&mutex);
let _ = panic::catch_unwind(AssertUnwindSafe(move || {
let _guard = m.lock().unwrap();
panic!("poison it");
}));
}
let handles: Vec<_> = (0..4)
.map(|i| {
let m = Arc::clone(&mutex);
thread::spawn(move || {
let mut guard = lock_recover(&m);
*guard += 1;
println!("[Thread {i}] Incremented to {}", *guard);
})
})
.collect();
for h in handles {
h.join().unwrap();
}
let final_val = *lock_recover(&mutex);
println!("[TEST] Final value after 4 increments: {final_val}");
assert_eq!(final_val, 4);
println!("[TEST] PASS: Concurrent recovery works");
}
}