Skip to main content

acls_rs/
sync.rs

1//! Generic synchronization strategy for interior mutability.
2//!
3//! This module provides a trait abstraction over `Mutex<T>` and `RefCell<T>`
4//! to enable compile-time selection between thread-safe and single-threaded variants.
5
6use std::cell::RefCell;
7use std::sync::Mutex;
8
9/// A synchronization strategy for providing interior mutability.
10///
11/// This trait abstracts over `Mutex<T>` (thread-safe) and `RefCell<T>` (single-threaded)
12/// to allow generic code to work with either synchronization primitive.
13///
14/// # Type Parameters
15///
16/// * `T` - The type being protected by the synchronization primitive
17///
18/// # Examples
19///
20/// ```
21/// use acls_rs::sync::SyncStrategy;
22/// use std::sync::Mutex;
23/// use std::cell::RefCell;
24///
25/// fn use_cache<S: SyncStrategy<Vec<String>>>(cache: &S) -> usize {
26///     cache.with(|data| data.len())
27/// }
28///
29/// // Thread-safe variant
30/// let cache = Mutex::new(vec!["a".to_string()]);
31/// assert_eq!(use_cache(&cache), 1);
32///
33/// // Single-threaded variant
34/// let cache = RefCell::new(vec!["a".to_string()]);
35/// assert_eq!(use_cache(&cache), 1);
36/// ```
37pub trait SyncStrategy<T>: Sized {
38    /// Wrap a value in this synchronization primitive.
39    ///
40    /// # Arguments
41    ///
42    /// * `inner` - The value to protect
43    ///
44    /// # Returns
45    ///
46    /// A new instance wrapping the value
47    fn new(inner: T) -> Self;
48
49    /// Execute a closure with mutable access to the protected value.
50    ///
51    /// For `Mutex`, this acquires the lock. For `RefCell`, this borrows mutably.
52    ///
53    /// # Arguments
54    ///
55    /// * `f` - Closure that receives mutable access to the value
56    ///
57    /// # Returns
58    ///
59    /// The result of the closure
60    ///
61    /// # Panics
62    ///
63    /// For `RefCell`, panics if already borrowed.
64    /// For `Mutex`, handles poisoned mutexes by logging a warning and recovering.
65    fn with<R>(&self, f: impl FnOnce(&mut T) -> R) -> R;
66}
67
68/// Thread-safe synchronization using `Mutex`.
69///
70/// Automatically handles poisoned mutexes by logging a warning and recovering
71/// the inner value. This prevents cascading failures when a thread panics while
72/// holding the lock.
73impl<T> SyncStrategy<T> for Mutex<T> {
74    fn new(inner: T) -> Self {
75        Mutex::new(inner)
76    }
77
78    fn with<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
79        let mut guard = match self.lock() {
80            Ok(g) => g,
81            Err(e) => {
82                log::warn!(
83                    "acls: mutex was poisoned (a thread panicked while holding the lock). \
84                     Recovering, but state may be inconsistent."
85                );
86                e.into_inner()
87            }
88        };
89        f(&mut guard)
90    }
91}
92
93/// Single-threaded synchronization using `RefCell`.
94///
95/// Provides zero-cost interior mutability for single-threaded use cases.
96/// Use this variant when you know the type will only be used from one thread.
97impl<T> SyncStrategy<T> for RefCell<T> {
98    fn new(inner: T) -> Self {
99        RefCell::new(inner)
100    }
101
102    fn with<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
103        f(&mut self.borrow_mut())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_mutex_strategy() {
113        let cache = Mutex::new(Vec::<String>::new());
114        cache.with(|data| data.push("test".to_string()));
115        cache.with(|data| {
116            assert_eq!(data.len(), 1);
117            assert_eq!(data[0], "test");
118        });
119    }
120
121    #[test]
122    fn test_refcell_strategy() {
123        let cache = RefCell::new(Vec::<String>::new());
124        cache.with(|data| data.push("test".to_string()));
125        cache.with(|data| {
126            assert_eq!(data.len(), 1);
127            assert_eq!(data[0], "test");
128        });
129    }
130
131    #[test]
132    fn test_generic_function() {
133        fn count_items<S: SyncStrategy<Vec<i32>>>(storage: &S) -> usize {
134            storage.with(|data| data.len())
135        }
136
137        let mutex_storage = Mutex::new(vec![1, 2, 3]);
138        let refcell_storage = RefCell::new(vec![1, 2, 3]);
139
140        assert_eq!(count_items(&mutex_storage), 3);
141        assert_eq!(count_items(&refcell_storage), 3);
142    }
143}