role_system/
performance.rs

1//! Performance optimization utilities for the role system.
2
3use std::borrow::Cow;
4use std::collections::VecDeque;
5use std::sync::Mutex;
6
7/// String pool for reducing allocations in hot paths.
8pub struct StringPool {
9    pool: Mutex<VecDeque<String>>,
10    max_size: usize,
11}
12
13impl StringPool {
14    /// Create a new string pool with the specified maximum size.
15    pub fn new(max_size: usize) -> Self {
16        Self {
17            pool: Mutex::new(VecDeque::with_capacity(max_size)),
18            max_size,
19        }
20    }
21
22    /// Get a string from the pool, or create a new one if the pool is empty.
23    pub fn get_string(&self) -> String {
24        self.pool.lock().unwrap().pop_front().unwrap_or_default()
25    }
26
27    /// Return a string to the pool after clearing it.
28    pub fn return_string(&self, mut s: String) {
29        s.clear();
30        let mut pool = self.pool.lock().unwrap();
31        if pool.len() < self.max_size {
32            pool.push_back(s);
33        }
34    }
35
36    /// Get the current pool size.
37    pub fn pool_size(&self) -> usize {
38        self.pool.lock().unwrap().len()
39    }
40
41    /// Clear the entire pool.
42    pub fn clear(&self) {
43        self.pool.lock().unwrap().clear();
44    }
45}
46
47impl Default for StringPool {
48    fn default() -> Self {
49        Self::new(100) // Default pool size
50    }
51}
52
53/// Optimized cache key that uses Cow for conditional cloning.
54#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub struct CacheKey<'a> {
56    pub subject_id: Cow<'a, str>,
57    pub permission_key: Cow<'a, str>,
58}
59
60impl<'a> CacheKey<'a> {
61    /// Create a new cache key with borrowed strings.
62    pub fn borrowed(subject_id: &'a str, permission_key: &'a str) -> Self {
63        Self {
64            subject_id: Cow::Borrowed(subject_id),
65            permission_key: Cow::Borrowed(permission_key),
66        }
67    }
68
69    /// Create a new cache key with owned strings.
70    pub fn owned(subject_id: String, permission_key: String) -> Self {
71        Self {
72            subject_id: Cow::Owned(subject_id),
73            permission_key: Cow::Owned(permission_key),
74        }
75    }
76
77    /// Convert to owned cache key.
78    pub fn into_owned(self) -> CacheKey<'static> {
79        CacheKey {
80            subject_id: Cow::Owned(self.subject_id.into_owned()),
81            permission_key: Cow::Owned(self.permission_key.into_owned()),
82        }
83    }
84}
85
86/// Memory pool for frequently allocated objects.
87pub struct ObjectPool<T> {
88    pool: Mutex<VecDeque<T>>,
89    factory: Box<dyn Fn() -> T + Send + Sync>,
90    reset: Box<dyn Fn(&mut T) + Send + Sync>,
91    max_size: usize,
92}
93
94impl<T> ObjectPool<T> {
95    /// Create a new object pool.
96    pub fn new<F, R>(max_size: usize, factory: F, reset: R) -> Self
97    where
98        F: Fn() -> T + Send + Sync + 'static,
99        R: Fn(&mut T) + Send + Sync + 'static,
100    {
101        Self {
102            pool: Mutex::new(VecDeque::with_capacity(max_size)),
103            factory: Box::new(factory),
104            reset: Box::new(reset),
105            max_size,
106        }
107    }
108
109    /// Get an object from the pool.
110    pub fn get<'a>(&'a self) -> PooledObject<'a, T> {
111        let obj = self
112            .pool
113            .lock()
114            .unwrap()
115            .pop_front()
116            .unwrap_or_else(|| (self.factory)());
117
118        PooledObject::new(obj, self)
119    }
120
121    /// Return an object to the pool.
122    fn return_object(&self, mut obj: T) {
123        (self.reset)(&mut obj);
124        let mut pool = self.pool.lock().unwrap();
125        if pool.len() < self.max_size {
126            pool.push_back(obj);
127        }
128    }
129
130    /// Get current pool size.
131    pub fn pool_size(&self) -> usize {
132        self.pool.lock().unwrap().len()
133    }
134}
135
136/// RAII wrapper for pooled objects.
137pub struct PooledObject<'a, T> {
138    obj: Option<T>,
139    pool: &'a ObjectPool<T>,
140}
141
142impl<'a, T> PooledObject<'a, T> {
143    fn new(obj: T, pool: &'a ObjectPool<T>) -> Self {
144        Self {
145            obj: Some(obj),
146            pool,
147        }
148    }
149}
150
151impl<'a, T> std::ops::Deref for PooledObject<'a, T> {
152    type Target = T;
153
154    fn deref(&self) -> &Self::Target {
155        self.obj.as_ref().unwrap()
156    }
157}
158
159impl<'a, T> std::ops::DerefMut for PooledObject<'a, T> {
160    fn deref_mut(&mut self) -> &mut Self::Target {
161        self.obj.as_mut().unwrap()
162    }
163}
164
165impl<'a, T> Drop for PooledObject<'a, T> {
166    fn drop(&mut self) {
167        if let Some(obj) = self.obj.take() {
168            self.pool.return_object(obj);
169        }
170    }
171}
172
173/// Optimized string operations for frequent use.
174pub mod string_ops {
175    use super::*;
176    use std::collections::HashMap;
177
178    thread_local! {
179        static STRING_POOL: StringPool = StringPool::default();
180    }
181
182    /// Get a string from the thread-local pool.
183    pub fn get_pooled_string() -> String {
184        STRING_POOL.with(|pool| pool.get_string())
185    }
186
187    /// Return a string to the thread-local pool.
188    pub fn return_pooled_string(s: String) {
189        STRING_POOL.with(|pool| pool.return_string(s));
190    }
191
192    /// Create an optimized permission key with minimal allocations.
193    pub fn create_permission_key(action: &str, resource_id: &str, context_hash: &str) -> String {
194        if context_hash.is_empty() {
195            format!("{}:{}", action, resource_id)
196        } else {
197            format!("{}:{}:{}", action, resource_id, context_hash)
198        }
199    }
200
201    /// Create a simple hash of context for caching.
202    pub fn hash_context(context: &HashMap<String, String>) -> String {
203        if context.is_empty() {
204            String::new()
205        } else {
206            use std::collections::hash_map::DefaultHasher;
207            use std::hash::{Hash, Hasher};
208
209            let mut hasher = DefaultHasher::new();
210
211            // Sort keys for consistent hashing
212            let mut sorted_keys: Vec<_> = context.keys().collect();
213            sorted_keys.sort();
214
215            for key in sorted_keys {
216                key.hash(&mut hasher);
217                context[key].hash(&mut hasher);
218            }
219
220            format!("{:x}", hasher.finish())
221        }
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use std::collections::HashMap;
229
230    #[test]
231    fn test_string_pool() {
232        let pool = StringPool::new(5);
233
234        // Get and return strings
235        let s1 = pool.get_string();
236        let s2 = pool.get_string();
237
238        pool.return_string(s1);
239        pool.return_string(s2);
240
241        assert_eq!(pool.pool_size(), 2);
242
243        // Pool should not exceed max size
244        for _ in 0..10 {
245            pool.return_string(String::new());
246        }
247        assert_eq!(pool.pool_size(), 5);
248    }
249
250    #[test]
251    fn test_cache_key() {
252        let key1 = CacheKey::borrowed("user1", "read:docs");
253        let key2 = CacheKey::owned("user1".to_string(), "read:docs".to_string());
254
255        assert_eq!(key1, key2);
256
257        let owned_key = key1.into_owned();
258        assert_eq!(owned_key.subject_id, "user1");
259    }
260
261    #[test]
262    fn test_object_pool() {
263        let pool = ObjectPool::new(3, Vec::<i32>::new, |v| v.clear());
264
265        {
266            let mut obj1 = pool.get();
267            obj1.push(42);
268            assert_eq!(obj1[0], 42);
269        } // obj1 is returned to pool here
270
271        {
272            let obj2 = pool.get();
273            assert!(obj2.is_empty()); // Should be reset
274        }
275
276        assert_eq!(pool.pool_size(), 1);
277    }
278
279    #[test]
280    fn test_string_ops() {
281        use super::string_ops::*;
282
283        let key = create_permission_key("read", "doc1", "");
284        assert_eq!(key, "read:doc1");
285
286        let key_with_context = create_permission_key("read", "doc1", "abc123");
287        assert_eq!(key_with_context, "read:doc1:abc123");
288
289        let mut context = HashMap::new();
290        context.insert("user".to_string(), "admin".to_string());
291        context.insert("time".to_string(), "day".to_string());
292
293        let hash1 = hash_context(&context);
294        let hash2 = hash_context(&context);
295        assert_eq!(hash1, hash2); // Should be consistent
296
297        let empty_hash = hash_context(&HashMap::new());
298        assert!(empty_hash.is_empty());
299    }
300}