offline_protocol_core/
sync.rs1use std::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
16
17pub trait MutexExt<T> {
19 fn lock_or_recover(&self) -> MutexGuard<'_, T>;
24}
25
26impl<T> MutexExt<T> for Mutex<T> {
27 #[track_caller]
28 fn lock_or_recover(&self) -> MutexGuard<'_, T> {
29 let location = std::panic::Location::caller();
30 self.lock().unwrap_or_else(|e| {
31 tracing::warn!(location = %location, "Mutex poisoned — recovering with inner value");
32 e.into_inner()
33 })
34 }
35}
36
37pub trait RwLockExt<T> {
39 fn read_or_recover(&self) -> RwLockReadGuard<'_, T>;
44
45 fn write_or_recover(&self) -> RwLockWriteGuard<'_, T>;
50}
51
52impl<T> RwLockExt<T> for RwLock<T> {
53 #[track_caller]
54 fn read_or_recover(&self) -> RwLockReadGuard<'_, T> {
55 let location = std::panic::Location::caller();
56 self.read().unwrap_or_else(|e| {
57 tracing::warn!(location = %location, "RwLock poisoned — recovering with inner value");
58 e.into_inner()
59 })
60 }
61
62 #[track_caller]
63 fn write_or_recover(&self) -> RwLockWriteGuard<'_, T> {
64 let location = std::panic::Location::caller();
65 self.write().unwrap_or_else(|e| {
66 tracing::warn!(location = %location, "RwLock poisoned — recovering with inner value");
67 e.into_inner()
68 })
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use std::sync::Arc;
76 use std::thread;
77
78 fn poison_mutex(lock: &Arc<Mutex<Vec<u32>>>) {
79 let cloned = Arc::clone(lock);
80 let _ = thread::spawn(move || {
81 let _guard = cloned.lock().unwrap();
82 panic!("poison the lock");
83 })
84 .join();
85 }
86
87 #[test]
88 fn mutex_recovers_after_poison() {
89 let lock = Arc::new(Mutex::new(vec![1u32]));
90 poison_mutex(&lock);
91 assert!(lock.is_poisoned());
92
93 let mut guard = lock.lock_or_recover();
94 guard.push(2);
95 assert_eq!(*guard, vec![1, 2]);
96 drop(guard);
97
98 assert_eq!(lock.lock_or_recover().len(), 2);
100 }
101
102 #[test]
103 fn mutex_unpoisoned_behaves_normally() {
104 let lock = Mutex::new(7u32);
105 assert_eq!(*lock.lock_or_recover(), 7);
106 }
107
108 #[test]
109 fn rwlock_recovers_after_poison() {
110 let lock = Arc::new(RwLock::new(vec![1u32]));
111 let cloned = Arc::clone(&lock);
112 let _ = thread::spawn(move || {
113 let _guard = cloned.write().unwrap();
114 panic!("poison the lock");
115 })
116 .join();
117 assert!(lock.is_poisoned());
118
119 lock.write_or_recover().push(2);
120 assert_eq!(*lock.read_or_recover(), vec![1, 2]);
121 }
122}