codeprism_storage/
cache.rs

1//! Cache storage implementations
2
3use crate::{CacheStats, CacheStorage};
4use anyhow::Result;
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, SystemTime};
10
11/// LRU cache entry with TTL support
12#[derive(Debug, Clone)]
13struct CacheEntry {
14    data: Vec<u8>,
15    last_accessed: SystemTime,
16    expires_at: Option<SystemTime>,
17}
18
19/// In-memory LRU cache storage
20pub struct LruCacheStorage {
21    cache: Arc<Mutex<HashMap<String, CacheEntry>>>,
22    max_size_bytes: usize,
23    current_size_bytes: Arc<Mutex<usize>>,
24    stats: Arc<Mutex<CacheStats>>,
25}
26
27impl LruCacheStorage {
28    /// Create a new LRU cache with the specified maximum size in bytes
29    pub fn new(max_size_bytes: usize) -> Self {
30        Self {
31            cache: Arc::new(Mutex::new(HashMap::new())),
32            max_size_bytes,
33            current_size_bytes: Arc::new(Mutex::new(0)),
34            stats: Arc::new(Mutex::new(CacheStats {
35                total_keys: 0,
36                memory_usage_bytes: 0,
37                hit_count: 0,
38                miss_count: 0,
39                eviction_count: 0,
40            })),
41        }
42    }
43
44    /// Evict expired entries
45    fn evict_expired(&self) -> Result<()> {
46        let now = SystemTime::now();
47        let mut cache = self.cache.lock().unwrap();
48        let mut current_size = self.current_size_bytes.lock().unwrap();
49        let mut stats = self.stats.lock().unwrap();
50
51        let keys_to_remove: Vec<String> = cache
52            .iter()
53            .filter_map(|(key, entry)| {
54                if let Some(expires_at) = entry.expires_at {
55                    if now > expires_at {
56                        Some(key.clone())
57                    } else {
58                        None
59                    }
60                } else {
61                    None
62                }
63            })
64            .collect();
65
66        for key in keys_to_remove {
67            if let Some(entry) = cache.remove(&key) {
68                *current_size -= entry.data.len();
69                stats.eviction_count += 1;
70            }
71        }
72
73        stats.total_keys = cache.len();
74        stats.memory_usage_bytes = *current_size;
75
76        Ok(())
77    }
78
79    /// Evict least recently used entries to make space
80    fn evict_lru(&self, needed_space: usize) -> Result<()> {
81        let mut cache = self.cache.lock().unwrap();
82        let mut current_size = self.current_size_bytes.lock().unwrap();
83        let mut stats = self.stats.lock().unwrap();
84
85        while *current_size + needed_space > self.max_size_bytes && !cache.is_empty() {
86            // Find the least recently used entry
87            let lru_key = cache
88                .iter()
89                .min_by_key(|(_, entry)| entry.last_accessed)
90                .map(|(key, _)| key.clone());
91
92            if let Some(key) = lru_key {
93                if let Some(entry) = cache.remove(&key) {
94                    *current_size -= entry.data.len();
95                    stats.eviction_count += 1;
96                }
97            } else {
98                break;
99            }
100        }
101
102        stats.total_keys = cache.len();
103        stats.memory_usage_bytes = *current_size;
104
105        Ok(())
106    }
107}
108
109#[async_trait]
110impl CacheStorage for LruCacheStorage {
111    async fn get<T>(&self, key: &str) -> Result<Option<T>>
112    where
113        T: for<'de> Deserialize<'de> + Send,
114    {
115        // First evict expired entries
116        self.evict_expired()?;
117
118        let mut cache = self.cache.lock().unwrap();
119        let mut stats = self.stats.lock().unwrap();
120
121        if let Some(entry) = cache.get_mut(key) {
122            // Check if entry has expired
123            if let Some(expires_at) = entry.expires_at {
124                if SystemTime::now() > expires_at {
125                    cache.remove(key);
126                    stats.miss_count += 1;
127                    stats.total_keys = cache.len();
128                    return Ok(None);
129                }
130            }
131
132            // Update last accessed time
133            entry.last_accessed = SystemTime::now();
134            stats.hit_count += 1;
135
136            // Deserialize the data
137            let value: T = bincode::deserialize(&entry.data)?;
138            Ok(Some(value))
139        } else {
140            stats.miss_count += 1;
141            Ok(None)
142        }
143    }
144
145    async fn set<T>(&self, key: &str, value: &T, ttl: Option<Duration>) -> Result<()>
146    where
147        T: Serialize + Send + Sync,
148    {
149        // First evict expired entries
150        self.evict_expired()?;
151
152        let serialized = bincode::serialize(value)?;
153        let needed_space = serialized.len();
154
155        // Evict LRU entries if needed
156        self.evict_lru(needed_space)?;
157
158        let expires_at = ttl.map(|duration| SystemTime::now() + duration);
159
160        let entry = CacheEntry {
161            data: serialized,
162            last_accessed: SystemTime::now(),
163            expires_at,
164        };
165
166        let mut cache = self.cache.lock().unwrap();
167        let mut current_size = self.current_size_bytes.lock().unwrap();
168        let mut stats = self.stats.lock().unwrap();
169
170        // Remove old entry if it exists
171        if let Some(old_entry) = cache.remove(key) {
172            *current_size -= old_entry.data.len();
173        }
174
175        // Add new entry
176        *current_size += entry.data.len();
177        cache.insert(key.to_string(), entry);
178
179        stats.total_keys = cache.len();
180        stats.memory_usage_bytes = *current_size;
181
182        Ok(())
183    }
184
185    async fn delete(&self, key: &str) -> Result<()> {
186        let mut cache = self.cache.lock().unwrap();
187        let mut current_size = self.current_size_bytes.lock().unwrap();
188        let mut stats = self.stats.lock().unwrap();
189
190        if let Some(entry) = cache.remove(key) {
191            *current_size -= entry.data.len();
192        }
193
194        stats.total_keys = cache.len();
195        stats.memory_usage_bytes = *current_size;
196
197        Ok(())
198    }
199
200    async fn invalidate_pattern(&self, pattern: &str) -> Result<()> {
201        let mut cache = self.cache.lock().unwrap();
202        let mut current_size = self.current_size_bytes.lock().unwrap();
203        let mut stats = self.stats.lock().unwrap();
204
205        let keys_to_remove: Vec<String> = cache
206            .keys()
207            .filter(|key| key.contains(pattern))
208            .cloned()
209            .collect();
210
211        for key in keys_to_remove {
212            if let Some(entry) = cache.remove(&key) {
213                *current_size -= entry.data.len();
214            }
215        }
216
217        stats.total_keys = cache.len();
218        stats.memory_usage_bytes = *current_size;
219
220        Ok(())
221    }
222
223    async fn get_stats(&self) -> Result<CacheStats> {
224        let stats = self.stats.lock().unwrap();
225        Ok(stats.clone())
226    }
227
228    async fn clear(&self) -> Result<()> {
229        let mut cache = self.cache.lock().unwrap();
230        let mut current_size = self.current_size_bytes.lock().unwrap();
231        let mut stats = self.stats.lock().unwrap();
232
233        cache.clear();
234        *current_size = 0;
235
236        stats.total_keys = 0;
237        stats.memory_usage_bytes = 0;
238
239        Ok(())
240    }
241}