mermaid_cli/utils/
mutex_ext.rs

1use std::sync::{Arc, Mutex};
2use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
3
4/// Extension trait for Mutex to provide safe lock operations
5pub trait MutexExt<T> {
6    /// Lock the mutex, recovering from poison errors
7    /// This is safer than unwrap() as it handles poisoned mutexes gracefully
8    fn lock_safe(&self) -> T
9    where
10        T: Clone;
11
12    /// Lock the mutex for mutation, recovering from poison errors
13    fn lock_mut_safe(&self) -> std::sync::MutexGuard<'_, T>;
14}
15
16impl<T: Clone> MutexExt<T> for Mutex<T> {
17    fn lock_safe(&self) -> T {
18        match self.lock() {
19            Ok(guard) => guard.clone(),
20            Err(poisoned) => {
21                // Mutex was poisoned due to a panic in another thread
22                // We can still access the data, but we log the issue
23                eprintln!("[WARNING] Mutex was poisoned, recovering data");
24                poisoned.into_inner().clone()
25            },
26        }
27    }
28
29    fn lock_mut_safe(&self) -> std::sync::MutexGuard<'_, T> {
30        match self.lock() {
31            Ok(guard) => guard,
32            Err(poisoned) => {
33                // Mutex was poisoned, but we can still get the guard
34                eprintln!("[WARNING] Mutex was poisoned, recovering guard");
35                poisoned.into_inner()
36            },
37        }
38    }
39}
40
41/// Safe lock for Arc<Mutex<T>> - standalone function since we can't impl on Arc
42pub fn lock_arc_mutex_safe<T>(mutex: &Arc<Mutex<T>>) -> std::sync::MutexGuard<'_, T> {
43    match mutex.lock() {
44        Ok(guard) => guard,
45        Err(poisoned) => {
46            eprintln!("[WARNING] Arc<Mutex> was poisoned, recovering guard");
47            poisoned.into_inner()
48        },
49    }
50}
51
52/// Helper to lock a mutex with expect-style error message
53#[macro_export]
54macro_rules! lock_or_panic {
55    ($mutex:expr, $msg:expr) => {
56        $mutex.lock().unwrap_or_else(|e| {
57            panic!("{}: mutex poisoned: {}", $msg, e);
58        })
59    };
60}
61
62/// Extension trait for RwLock to provide convenient async lock operations
63/// Unlike Mutex, RwLock doesn't poison, so no recovery logic needed
64#[allow(dead_code)]
65pub trait RwLockExt<T> {
66    /// Acquire a read lock (shared access, multiple readers allowed)
67    async fn read_safe<'a>(&'a self) -> RwLockReadGuard<'a, T>
68    where
69        T: 'a;
70
71    /// Acquire a write lock (exclusive access, blocks all readers and writers)
72    async fn write_safe<'a>(&'a self) -> RwLockWriteGuard<'a, T>
73    where
74        T: 'a;
75}
76
77impl<T> RwLockExt<T> for RwLock<T> {
78    async fn read_safe<'a>(&'a self) -> RwLockReadGuard<'a, T>
79    where
80        T: 'a,
81    {
82        self.read().await
83    }
84
85    async fn write_safe<'a>(&'a self) -> RwLockWriteGuard<'a, T>
86    where
87        T: 'a,
88    {
89        self.write().await
90    }
91}
92
93/// Helper for Arc<RwLock<T>> - common pattern in concurrent code
94impl<T> RwLockExt<T> for Arc<RwLock<T>> {
95    async fn read_safe<'a>(&'a self) -> RwLockReadGuard<'a, T>
96    where
97        T: 'a,
98    {
99        self.read().await
100    }
101
102    async fn write_safe<'a>(&'a self) -> RwLockWriteGuard<'a, T>
103    where
104        T: 'a,
105    {
106        self.write().await
107    }
108}