memscope_rs/core/
simple_mutex.rs

1//! Simple mutex implementation with compile-time optimization selection
2//!
3//! This module provides a simplified mutex that avoids runtime overhead
4//! by using compile-time feature selection.
5
6/// Optimized mutex type selected at compile time
7#[cfg(feature = "parking-lot")]
8pub type OptimizedMutex<T> = parking_lot::Mutex<T>;
9
10#[cfg(not(feature = "parking-lot"))]
11pub type OptimizedMutex<T> = std::sync::Mutex<T>;
12
13/// Simple mutex wrapper that provides consistent API
14pub struct SimpleMutex<T> {
15    inner: OptimizedMutex<T>,
16    #[cfg(debug_assertions)]
17    access_count: std::sync::atomic::AtomicU64,
18}
19
20impl<T> SimpleMutex<T> {
21    /// Create new simple mutex
22    pub fn new(data: T) -> Self {
23        Self {
24            inner: OptimizedMutex::new(data),
25            #[cfg(debug_assertions)]
26            access_count: std::sync::atomic::AtomicU64::new(0),
27        }
28    }
29
30    /// Lock the mutex
31    #[cfg(feature = "parking-lot")]
32    pub fn lock(&self) -> parking_lot::MutexGuard<'_, T> {
33        #[cfg(debug_assertions)]
34        self.access_count
35            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
36
37        self.inner.lock()
38    }
39
40    /// Lock the mutex
41    #[cfg(not(feature = "parking-lot"))]
42    pub fn lock(
43        &self,
44    ) -> Result<std::sync::MutexGuard<T>, std::sync::PoisonError<std::sync::MutexGuard<T>>> {
45        #[cfg(debug_assertions)]
46        self.access_count
47            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
48
49        self.inner.lock()
50    }
51
52    /// Try to lock the mutex
53    #[cfg(feature = "parking-lot")]
54    pub fn try_lock(&self) -> Option<parking_lot::MutexGuard<'_, T>> {
55        #[cfg(debug_assertions)]
56        self.access_count
57            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
58
59        self.inner.try_lock()
60    }
61
62    /// Try to lock the mutex
63    #[cfg(not(feature = "parking-lot"))]
64    pub fn try_lock(
65        &self,
66    ) -> Result<std::sync::MutexGuard<T>, std::sync::TryLockError<std::sync::MutexGuard<T>>> {
67        #[cfg(debug_assertions)]
68        self.access_count
69            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
70
71        self.inner.try_lock()
72    }
73
74    /// Get access count (debug only)
75    #[cfg(debug_assertions)]
76    pub fn access_count(&self) -> u64 {
77        self.access_count.load(std::sync::atomic::Ordering::Relaxed)
78    }
79
80    /// Get access count (release mode - always returns 0)
81    #[cfg(not(debug_assertions))]
82    pub fn access_count(&self) -> u64 {
83        0
84    }
85}
86
87impl<T: Default> Default for SimpleMutex<T> {
88    fn default() -> Self {
89        Self::new(T::default())
90    }
91}
92
93// SimpleMutex uses std::sync::Mutex internally, so we provide safe_lock methods
94impl<T> SimpleMutex<T> {
95    /// Safe lock that returns Result for both parking-lot and std
96    #[cfg(feature = "parking-lot")]
97    pub fn safe_lock(&self) -> crate::core::types::TrackingResult<parking_lot::MutexGuard<'_, T>> {
98        // parking-lot's lock() never fails
99        Ok(self.lock())
100    }
101
102    #[cfg(not(feature = "parking-lot"))]
103    pub fn safe_lock(&self) -> crate::core::types::TrackingResult<std::sync::MutexGuard<'_, T>> {
104        // std::sync::Mutex's lock() returns Result<MutexGuard, PoisonError>
105        self.lock().map_err(|_| {
106            crate::core::types::TrackingError::LockError("Failed to acquire mutex lock".to_string())
107        })
108    }
109
110    /// Safe try_lock that returns consistent Result for both parking-lot and std
111    #[cfg(feature = "parking-lot")]
112    pub fn try_safe_lock(
113        &self,
114    ) -> crate::core::types::TrackingResult<Option<parking_lot::MutexGuard<'_, T>>> {
115        // parking-lot's try_lock() returns Option<MutexGuard>
116        Ok(self.try_lock())
117    }
118
119    #[cfg(not(feature = "parking-lot"))]
120    pub fn try_safe_lock(
121        &self,
122    ) -> crate::core::types::TrackingResult<Option<std::sync::MutexGuard<'_, T>>> {
123        // std::sync::Mutex's try_lock() returns Result<MutexGuard, TryLockError>
124        match self.try_lock() {
125            Ok(guard) => Ok(Some(guard)),
126            Err(std::sync::TryLockError::WouldBlock) => Ok(None),
127            Err(_) => Err(crate::core::types::TrackingError::LockError(
128                "Failed to try acquire mutex lock".to_string(),
129            )),
130        }
131    }
132}
133
134// Safety: SimpleMutex is Send if T is Send
135unsafe impl<T: Send> Send for SimpleMutex<T> {}
136
137// Safety: SimpleMutex is Sync if T is Send
138unsafe impl<T: Send> Sync for SimpleMutex<T> {}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use std::sync::Arc;
144    use std::thread;
145
146    #[test]
147    fn test_simple_mutex_creation() {
148        let mutex = SimpleMutex::new(42);
149        assert_eq!(mutex.access_count(), 0);
150    }
151
152    #[test]
153    fn test_simple_mutex_lock() {
154        let mutex = SimpleMutex::new(42);
155
156        #[cfg(feature = "parking-lot")]
157        {
158            let guard = mutex.lock();
159            assert_eq!(*guard, 42);
160        }
161
162        #[cfg(not(feature = "parking-lot"))]
163        {
164            let guard = mutex.lock().unwrap();
165            assert_eq!(*guard, 42);
166        }
167
168        #[cfg(debug_assertions)]
169        assert_eq!(mutex.access_count(), 1);
170    }
171
172    #[test]
173    fn test_simple_mutex_try_lock() {
174        let mutex = SimpleMutex::new(42);
175
176        #[cfg(feature = "parking-lot")]
177        {
178            let guard = mutex.try_lock();
179            assert!(guard.is_some());
180            assert_eq!(*guard.unwrap(), 42);
181        }
182
183        #[cfg(not(feature = "parking-lot"))]
184        {
185            let guard = mutex.try_lock();
186            assert!(guard.is_ok());
187            assert_eq!(*guard.unwrap(), 42);
188        }
189
190        #[cfg(debug_assertions)]
191        assert_eq!(mutex.access_count(), 1);
192    }
193
194    #[test]
195    fn test_simple_mutex_concurrent_access() {
196        let mutex = Arc::new(SimpleMutex::new(0));
197        let mut handles = vec![];
198
199        for _ in 0..10 {
200            let mutex_clone = Arc::clone(&mutex);
201            let handle = thread::spawn(move || {
202                #[cfg(feature = "parking-lot")]
203                {
204                    let mut guard = mutex_clone.lock();
205                    *guard += 1;
206                }
207
208                #[cfg(not(feature = "parking-lot"))]
209                {
210                    let mut guard = mutex_clone
211                        .lock()
212                        .expect("Mutex should not be poisoned in thread");
213                    *guard += 1;
214                }
215            });
216            handles.push(handle);
217        }
218
219        for handle in handles {
220            handle.join().expect("Thread should complete successfully");
221        }
222
223        #[cfg(feature = "parking-lot")]
224        {
225            let guard = mutex.lock();
226            assert_eq!(*guard, 10);
227        }
228
229        #[cfg(not(feature = "parking-lot"))]
230        {
231            let guard = mutex.lock().unwrap();
232            assert_eq!(*guard, 10);
233        }
234    }
235
236    #[test]
237    fn test_simple_mutex_default() {
238        let mutex: SimpleMutex<i32> = SimpleMutex::default();
239
240        #[cfg(feature = "parking-lot")]
241        {
242            let guard = mutex.lock();
243            assert_eq!(*guard, 0);
244        }
245
246        #[cfg(not(feature = "parking-lot"))]
247        {
248            let guard = mutex.lock().unwrap();
249            assert_eq!(*guard, 0);
250        }
251    }
252
253    #[test]
254    #[cfg(debug_assertions)]
255    fn test_access_count_tracking() {
256        let mutex = SimpleMutex::new(42);
257        assert_eq!(mutex.access_count(), 0);
258
259        #[cfg(feature = "parking-lot")]
260        {
261            let _guard1 = mutex.lock();
262            assert_eq!(mutex.access_count(), 1);
263
264            let _guard2 = mutex.try_lock();
265            assert_eq!(mutex.access_count(), 2);
266        }
267
268        #[cfg(not(feature = "parking-lot"))]
269        {
270            let _guard1 = mutex.lock().unwrap();
271            assert_eq!(mutex.access_count(), 1);
272
273            let _guard2 = mutex.try_lock().unwrap();
274            assert_eq!(mutex.access_count(), 2);
275        }
276    }
277
278    #[test]
279    #[cfg(not(debug_assertions))]
280    fn test_access_count_release_mode() {
281        let mutex = SimpleMutex::new(42);
282
283        #[cfg(feature = "parking-lot")]
284        {
285            let _guard = mutex.lock();
286        }
287
288        #[cfg(not(feature = "parking-lot"))]
289        {
290            let _guard = mutex.lock().unwrap();
291        }
292
293        // In release mode, access count should always be 0
294        assert_eq!(mutex.access_count(), 0);
295    }
296}