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}