ferron_common/util/
module_cache.rs

1use std::hash::Hasher;
2use std::sync::Arc;
3use std::{collections::HashMap, error::Error};
4
5use crate::config::{ServerConfiguration, ServerConfigurationEntries};
6
7/// A highly optimized cache that stores modules according to server configuration
8pub struct ModuleCache<T> {
9  // Use a single HashMap for O(1) average-case lookups
10  inner: HashMap<CacheKey, Arc<T>>,
11  properties: Box<[&'static str]>, // Box<[T]> is more memory efficient than Vec<T>
12}
13
14// Optimized cache key that implements fast hashing and comparison
15#[derive(Clone, PartialEq, Eq)]
16struct CacheKey {
17  // Pre-sorted entries for consistent hashing
18  entries: Box<[(String, Option<ServerConfigurationEntries>)]>,
19}
20
21impl std::hash::Hash for CacheKey {
22  fn hash<H: Hasher>(&self, state: &mut H) {
23    self.entries.hash(state);
24  }
25}
26
27impl CacheKey {
28  fn new(config: &ServerConfiguration, properties: &[&'static str]) -> Self {
29    let mut entries: Vec<_> = properties
30      .iter()
31      .map(|&prop| (prop.to_string(), config.entries.get(prop).cloned()))
32      .collect();
33
34    // Sort for consistent cache keys regardless of property order
35    entries.sort_by(|a, b| a.0.cmp(&b.0));
36
37    Self {
38      entries: entries.into_boxed_slice(),
39    }
40  }
41}
42
43#[allow(dead_code)]
44impl<T> ModuleCache<T> {
45  /// Creates a cache that stores modules per specific properties
46  pub fn new(properties: Vec<&'static str>) -> Self {
47    Self {
48      inner: HashMap::with_capacity(16), // Pre-allocate reasonable capacity
49      properties: properties.into_boxed_slice(),
50    }
51  }
52
53  /// Creates a cache with custom initial capacity
54  pub fn with_capacity(properties: Vec<&'static str>, capacity: usize) -> Self {
55    Self {
56      inner: HashMap::with_capacity(capacity),
57      properties: properties.into_boxed_slice(),
58    }
59  }
60
61  /// Obtains a module from cache, initializing if not present
62  /// This is now O(1) average case instead of O(n)
63  pub fn get_or_init<F, E>(&mut self, config: &ServerConfiguration, init_fn: F) -> Result<Arc<T>, E>
64  where
65    F: FnOnce(&ServerConfiguration) -> Result<Arc<T>, E>,
66    E: From<Box<dyn Error + Send + Sync>>,
67  {
68    let cache_key = CacheKey::new(config, &self.properties);
69
70    // Fast path: check if already cached
71    if let Some(cached_value) = self.inner.get(&cache_key) {
72      return Ok(cached_value.clone());
73    }
74
75    // Slow path: initialize and cache
76    let new_value = init_fn(config)?;
77    self.inner.insert(cache_key, new_value.clone());
78    Ok(new_value)
79  }
80
81  /// Non-mutable variant that only retrieves from cache
82  pub fn get(&self, config: &ServerConfiguration) -> Option<Arc<T>> {
83    let cache_key = CacheKey::new(config, &self.properties);
84    self.inner.get(&cache_key).cloned()
85  }
86
87  /// Clear the cache
88  pub fn clear(&mut self) {
89    self.inner.clear();
90  }
91
92  /// Get current cache size
93  pub fn len(&self) -> usize {
94    self.inner.len()
95  }
96
97  /// Check if cache is empty
98  pub fn is_empty(&self) -> bool {
99    self.inner.is_empty()
100  }
101
102  /// Reserve capacity for additional entries
103  pub fn reserve(&mut self, additional: usize) {
104    self.inner.reserve(additional);
105  }
106
107  /// Gets a module from cache, or creates one with the fallback function without caching
108  pub fn get_or<F, E>(&self, config: &ServerConfiguration, fallback_fn: F) -> Result<Arc<T>, E>
109  where
110    F: FnOnce(&ServerConfiguration) -> Result<Arc<T>, E>,
111  {
112    let cache_key = CacheKey::new(config, &self.properties);
113
114    // Check if already cached
115    if let Some(cached_value) = self.inner.get(&cache_key) {
116      return Ok(cached_value.clone());
117    }
118
119    // Not cached, use fallback function (but don't cache the result)
120    fallback_fn(config)
121  }
122}
123
124// Implement Default for convenience
125impl<T> Default for ModuleCache<T> {
126  fn default() -> Self {
127    Self::new(Vec::new())
128  }
129}
130
131#[cfg(test)]
132mod test {
133  use crate::config::{ServerConfigurationEntry, ServerConfigurationFilters, ServerConfigurationValue};
134
135  use super::*;
136
137  #[test]
138  fn module_loading_test() {
139    let module = 1;
140
141    let cache = ModuleCache::new(vec!["property"]);
142
143    let mut config_entries = HashMap::new();
144    config_entries.insert(
145      "property".to_string(),
146      ServerConfigurationEntries {
147        inner: vec![ServerConfigurationEntry {
148          values: vec![ServerConfigurationValue::String("something".to_string())],
149          props: HashMap::new(),
150        }],
151      },
152    );
153    let config = ServerConfiguration {
154      entries: config_entries,
155      filters: ServerConfigurationFilters {
156        is_host: true,
157        hostname: None,
158        ip: None,
159        port: None,
160        condition: None,
161        error_handler_status: None,
162      },
163      modules: vec![],
164    };
165
166    let mut config2_entries = HashMap::new();
167    config2_entries.insert(
168      "property".to_string(),
169      ServerConfigurationEntries {
170        inner: vec![ServerConfigurationEntry {
171          values: vec![ServerConfigurationValue::String("something".to_string())],
172          props: HashMap::new(),
173        }],
174      },
175    );
176    config2_entries.insert(
177      "ignore".to_string(),
178      ServerConfigurationEntries {
179        inner: vec![ServerConfigurationEntry {
180          values: vec![ServerConfigurationValue::String("something else".to_string())],
181          props: HashMap::new(),
182        }],
183      },
184    );
185    let config2 = ServerConfiguration {
186      entries: config2_entries,
187      filters: ServerConfigurationFilters {
188        is_host: true,
189        hostname: None,
190        ip: None,
191        port: Some(80),
192        condition: None,
193        error_handler_status: None,
194      },
195      modules: vec![],
196    };
197
198    assert_eq!(
199      cache
200        .get_or::<_, Box<dyn std::error::Error + Send + Sync>>(&config, |_config| Ok(Arc::new(module)))
201        .unwrap(),
202      Arc::new(module)
203    );
204
205    assert_eq!(
206      cache
207        .get_or::<_, Box<dyn std::error::Error + Send + Sync>>(&config2, |_config| Ok(Arc::new(module)))
208        .unwrap(),
209      Arc::new(module)
210    );
211  }
212
213  #[test]
214  fn should_cache_the_module() {
215    let module = 1;
216    let module2 = 2;
217
218    let mut cache = ModuleCache::new(vec!["property"]);
219
220    let mut config_entries = HashMap::new();
221    config_entries.insert(
222      "property".to_string(),
223      ServerConfigurationEntries {
224        inner: vec![ServerConfigurationEntry {
225          values: vec![ServerConfigurationValue::String("something".to_string())],
226          props: HashMap::new(),
227        }],
228      },
229    );
230    let config = ServerConfiguration {
231      entries: config_entries,
232      filters: ServerConfigurationFilters {
233        is_host: true,
234        hostname: None,
235        ip: None,
236        port: None,
237        condition: None,
238        error_handler_status: None,
239      },
240      modules: vec![],
241    };
242
243    let mut config2_entries = HashMap::new();
244    config2_entries.insert(
245      "property".to_string(),
246      ServerConfigurationEntries {
247        inner: vec![ServerConfigurationEntry {
248          values: vec![ServerConfigurationValue::String("something".to_string())],
249          props: HashMap::new(),
250        }],
251      },
252    );
253    config2_entries.insert(
254      "ignore".to_string(),
255      ServerConfigurationEntries {
256        inner: vec![ServerConfigurationEntry {
257          values: vec![ServerConfigurationValue::String("something else".to_string())],
258          props: HashMap::new(),
259        }],
260      },
261    );
262    let config2 = ServerConfiguration {
263      entries: config2_entries,
264      filters: ServerConfigurationFilters {
265        is_host: true,
266        hostname: None,
267        ip: None,
268        port: Some(80),
269        condition: None,
270        error_handler_status: None,
271      },
272      modules: vec![],
273    };
274
275    // Should initialize a module
276    assert_eq!(
277      cache
278        .get_or_init::<_, Box<dyn std::error::Error + Send + Sync>>(&config, |_config| Ok(Arc::new(module)))
279        .unwrap(),
280      Arc::new(module)
281    );
282
283    // Should obtain cached module (not initialize module2)
284    assert_eq!(
285      cache
286        .get_or_init::<_, Box<dyn std::error::Error + Send + Sync>>(&config2, |_config| Ok(Arc::new(module2)))
287        .unwrap(),
288      Arc::new(module)
289    );
290  }
291
292  #[test]
293  fn test_cache_operations() {
294    let mut cache = ModuleCache::with_capacity(vec!["test_prop"], 10);
295
296    let config = ServerConfiguration {
297      entries: HashMap::new(),
298      filters: ServerConfigurationFilters {
299        is_host: true,
300        hostname: None,
301        ip: None,
302        port: None,
303        condition: None,
304        error_handler_status: None,
305      },
306      modules: vec![],
307    };
308
309    assert!(cache.is_empty());
310    assert_eq!(cache.len(), 0);
311
312    // Use Box<dyn Error + Send + Sync> directly
313    let value = cache
314      .get_or_init::<_, Box<dyn std::error::Error + Send + Sync>>(&config, |_| Ok(Arc::new(42)))
315      .unwrap();
316    assert_eq!(*value, 42);
317    assert_eq!(cache.len(), 1);
318
319    // Test direct get
320    let cached = cache.get(&config).unwrap();
321    assert_eq!(*cached, 42);
322
323    cache.clear();
324    assert!(cache.is_empty());
325  }
326}