Skip to main content

anno/
sync.rs

1//! Synchronization primitives with conditional compilation.
2//!
3//! Provides a unified mutex interface that uses `parking_lot::Mutex` when
4//! the `production` feature is enabled, falling back to `std::sync::Mutex` otherwise.
5
6#[cfg(feature = "production")]
7use parking_lot::Mutex as ParkingLotMutex;
8
9#[cfg(not(feature = "production"))]
10use std::sync::Mutex as StdMutex;
11
12/// Mutex type that conditionally uses parking_lot or std::sync::Mutex.
13///
14/// When `production` feature is enabled, uses `parking_lot::Mutex` for better
15/// performance (1.5-3x faster on uncontended locks). Otherwise uses `std::sync::Mutex`.
16///
17/// # Example
18///
19/// ```rust
20/// use anno::sync::Mutex;
21///
22/// let data = Mutex::new(42);
23/// *data.lock() = 100;
24/// ```
25#[cfg(feature = "production")]
26pub type Mutex<T> = ParkingLotMutex<T>;
27
28/// Mutex type using std::sync::Mutex (default, no `production` feature).
29#[cfg(not(feature = "production"))]
30pub type Mutex<T> = StdMutex<T>;
31
32/// Lock a mutex and return the guard, handling poisoning gracefully.
33///
34/// For `parking_lot::Mutex`, this is just `mutex.lock()`.
35/// For `std::sync::Mutex`, this handles poisoning by recovering the guard.
36///
37/// # Example
38///
39/// ```rust
40/// use anno::sync::{Mutex, lock};
41///
42/// let mutex = Mutex::new(42);
43/// let guard = lock(&mutex);
44/// ```
45#[cfg(feature = "production")]
46pub fn lock<T>(mutex: &Mutex<T>) -> parking_lot::MutexGuard<'_, T> {
47    mutex.lock()
48}
49
50/// Lock a mutex using std::sync::Mutex, recovering from poisoning.
51#[cfg(not(feature = "production"))]
52pub fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
53    mutex.lock().unwrap_or_else(|e| e.into_inner())
54}
55
56/// Attempt to acquire a mutex lock without blocking.
57///
58/// Returns `Ok(guard)` if the lock was acquired immediately, or `Err` if the lock
59/// is currently held by another thread or if poisoning occurred.
60///
61/// For `parking_lot::Mutex`, this uses `try_lock()` which returns `None` if the lock
62/// is held, converting it to an error.
63/// For `std::sync::Mutex`, this uses `try_lock()` and converts `PoisonError` to our `Error` type.
64///
65/// # Example
66///
67/// ```rust
68/// use anno::sync::{Mutex, try_lock};
69/// use anno::Result;
70///
71/// let mutex = Mutex::new(42);
72/// match try_lock(&mutex) {
73///     Ok(guard) => println!("Lock acquired: {}", *guard),
74///     Err(e) => println!("Lock failed: {}", e),
75/// }
76/// ```
77#[cfg(feature = "production")]
78pub fn try_lock<T>(mutex: &Mutex<T>) -> crate::Result<parking_lot::MutexGuard<'_, T>> {
79    mutex
80        .try_lock()
81        .ok_or_else(|| crate::Error::Retrieval("Mutex lock failed: would block".to_string()))
82}
83
84/// Try to lock a mutex using std::sync::Mutex without blocking.
85#[cfg(not(feature = "production"))]
86pub fn try_lock<T>(mutex: &Mutex<T>) -> crate::Result<std::sync::MutexGuard<'_, T>> {
87    mutex.try_lock().map_err(|e| match e {
88        std::sync::TryLockError::Poisoned(poison) => {
89            crate::Error::Retrieval(format!("Mutex lock failed: poisoned - {}", poison))
90        }
91        std::sync::TryLockError::WouldBlock => {
92            crate::Error::Retrieval("Mutex lock failed: would block".to_string())
93        }
94    })
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_mutex_new_and_lock() {
103        let mutex = Mutex::new(42);
104        let guard = lock(&mutex);
105        assert_eq!(*guard, 42);
106    }
107
108    #[test]
109    fn test_mutex_modify_value() {
110        let mutex = Mutex::new(0);
111        {
112            let mut guard = lock(&mutex);
113            *guard = 100;
114        }
115        let guard = lock(&mutex);
116        assert_eq!(*guard, 100);
117    }
118
119    #[test]
120    fn test_try_lock_success() {
121        let mutex = Mutex::new("test");
122        let result = try_lock(&mutex);
123        assert!(result.is_ok());
124        assert_eq!(*result.unwrap(), "test");
125    }
126
127    #[test]
128    fn test_mutex_with_struct() {
129        #[derive(Debug, PartialEq)]
130        struct Data {
131            value: i32,
132            name: String,
133        }
134
135        let mutex = Mutex::new(Data {
136            value: 42,
137            name: "test".to_string(),
138        });
139
140        let guard = lock(&mutex);
141        assert_eq!(guard.value, 42);
142        assert_eq!(guard.name, "test");
143    }
144
145    #[test]
146    fn test_multiple_locks_sequential() {
147        let mutex = Mutex::new(0);
148
149        for i in 0..10 {
150            let mut guard = lock(&mutex);
151            *guard = i;
152        }
153
154        let guard = lock(&mutex);
155        assert_eq!(*guard, 9);
156    }
157
158    #[test]
159    fn test_try_lock_returns_correct_value() {
160        let mutex = Mutex::new(vec![1, 2, 3]);
161
162        // Must bind the guard explicitly to control its lifetime
163        let result = try_lock(&mutex);
164        match result {
165            Ok(guard) => {
166                assert_eq!(guard.len(), 3);
167                assert_eq!(guard[0], 1);
168            }
169            Err(_) => {
170                panic!("try_lock should succeed when not contested");
171            }
172        }
173    }
174}