cache_kit/backend/
inmemory.rs

1//! In-memory cache backend (default, thread-safe, async).
2//!
3//! Uses DashMap for lock-free concurrent access with per-key sharding.
4//! Automatically handles TTL expiration on access.
5
6use super::CacheBackend;
7use crate::error::Result;
8use dashmap::DashMap;
9use std::sync::Arc;
10use std::time::Duration;
11use std::time::Instant;
12
13/// In-memory cache entry with optional expiration.
14struct CacheEntry {
15    data: Vec<u8>,
16    expires_at: Option<Instant>,
17}
18
19impl CacheEntry {
20    fn new(data: Vec<u8>, ttl: Option<Duration>) -> Self {
21        let expires_at = ttl.map(|d| Instant::now() + d);
22        CacheEntry { data, expires_at }
23    }
24
25    fn is_expired(&self) -> bool {
26        self.expires_at.is_some_and(|exp| Instant::now() > exp)
27    }
28}
29
30/// Thread-safe async in-memory cache backend.
31///
32/// Uses DashMap for lock-free concurrent access with fine-grained per-key sharding.
33/// No async locks required - operations are non-blocking.
34/// Automatically handles TTL expiration on access.
35///
36/// # Example
37///
38/// ```no_run
39/// use cache_kit::backend::{InMemoryBackend, CacheBackend};
40/// use std::time::Duration;
41///
42/// #[tokio::main]
43/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
44///     let backend = InMemoryBackend::new();
45///
46///     // Store data
47///     backend.set("key1", b"value".to_vec(), None).await?;
48///
49///     // Retrieve data
50///     let value = backend.get("key1").await?;
51///     assert!(value.is_some());
52///
53///     // Store with TTL
54///     backend.set("key2", b"expires".to_vec(), Some(Duration::from_secs(300))).await?;
55///
56///     Ok(())
57/// }
58/// ```
59#[derive(Clone)]
60pub struct InMemoryBackend {
61    store: Arc<DashMap<String, CacheEntry>>,
62}
63
64impl InMemoryBackend {
65    /// Create a new in-memory cache backend.
66    pub fn new() -> Self {
67        InMemoryBackend {
68            store: Arc::new(DashMap::new()),
69        }
70    }
71
72    /// Get the current number of entries in cache.
73    pub async fn len(&self) -> usize {
74        self.store.len()
75    }
76
77    /// Check if cache is empty.
78    pub async fn is_empty(&self) -> bool {
79        self.store.is_empty()
80    }
81
82    /// Get memory statistics.
83    pub async fn stats(&self) -> CacheStats {
84        let total_bytes: usize = self.store.iter().map(|entry| entry.data.len()).sum();
85        let expired_count = self.store.iter().filter(|entry| entry.is_expired()).count();
86
87        CacheStats {
88            total_entries: self.store.len(),
89            expired_entries: expired_count,
90            total_bytes,
91        }
92    }
93
94    /// Print cache statistics to debug log.
95    pub async fn log_stats(&self) {
96        let stats = self.stats().await;
97        debug!(
98            "Cache Stats: {} entries ({} expired), {} bytes",
99            stats.total_entries, stats.expired_entries, stats.total_bytes
100        );
101    }
102}
103
104impl Default for InMemoryBackend {
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110impl CacheBackend for InMemoryBackend {
111    async fn get(&self, key: &str) -> Result<Option<Vec<u8>>> {
112        // Check if entry exists and is not expired
113        if let Some(entry) = self.store.get(key) {
114            if !entry.is_expired() {
115                debug!("✓ InMemory GET {} -> HIT", key);
116                return Ok(Some(entry.data.clone()));
117            }
118        }
119
120        // Remove expired entry if it exists
121        self.store.remove(key);
122        debug!("✓ InMemory GET {} -> MISS", key);
123        Ok(None)
124    }
125
126    async fn set(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<()> {
127        let entry = CacheEntry::new(value, ttl);
128        self.store.insert(key.to_string(), entry);
129
130        if let Some(d) = ttl {
131            debug!("✓ InMemory SET {} (TTL: {:?})", key, d);
132        } else {
133            debug!("✓ InMemory SET {}", key);
134        }
135
136        Ok(())
137    }
138
139    async fn delete(&self, key: &str) -> Result<()> {
140        self.store.remove(key);
141        debug!("✓ InMemory DELETE {}", key);
142        Ok(())
143    }
144
145    async fn exists(&self, key: &str) -> Result<bool> {
146        if let Some(entry) = self.store.get(key) {
147            return Ok(!entry.is_expired());
148        }
149
150        Ok(false)
151    }
152
153    async fn mget(&self, keys: &[&str]) -> Result<Vec<Option<Vec<u8>>>> {
154        let results: Vec<Option<Vec<u8>>> = keys
155            .iter()
156            .map(|k| {
157                if let Some(entry) = self.store.get(*k) {
158                    if entry.is_expired() {
159                        None
160                    } else {
161                        Some(entry.data.clone())
162                    }
163                } else {
164                    None
165                }
166            })
167            .collect();
168
169        debug!("✓ InMemory MGET {} keys", keys.len());
170        Ok(results)
171    }
172
173    async fn mdelete(&self, keys: &[&str]) -> Result<()> {
174        for key in keys {
175            self.store.remove(*key);
176        }
177
178        debug!("✓ InMemory MDELETE {} keys", keys.len());
179        Ok(())
180    }
181
182    async fn health_check(&self) -> Result<bool> {
183        // In-memory backend is always healthy
184        Ok(true)
185    }
186
187    async fn clear_all(&self) -> Result<()> {
188        self.store.clear();
189        warn!("⚠ InMemory CLEAR_ALL executed - all cache cleared!");
190        Ok(())
191    }
192}
193
194/// Cache statistics.
195#[derive(Clone, Debug)]
196pub struct CacheStats {
197    pub total_entries: usize,
198    pub expired_entries: usize,
199    pub total_bytes: usize,
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[tokio::test]
207    async fn test_inmemory_backend_set_get() {
208        let backend = InMemoryBackend::new();
209
210        backend
211            .set("key1", b"value1".to_vec(), None)
212            .await
213            .expect("Failed to set");
214
215        let result = backend.get("key1").await.expect("Failed to get");
216        assert_eq!(result, Some(b"value1".to_vec()));
217    }
218
219    #[tokio::test]
220    async fn test_inmemory_backend_miss() {
221        let backend = InMemoryBackend::new();
222
223        let result = backend.get("nonexistent").await.expect("Failed to get");
224        assert_eq!(result, None);
225    }
226
227    #[tokio::test]
228    async fn test_inmemory_backend_delete() {
229        let backend = InMemoryBackend::new();
230
231        backend
232            .set("key1", b"value1".to_vec(), None)
233            .await
234            .expect("Failed to set");
235        assert!(backend
236            .exists("key1")
237            .await
238            .expect("Failed to check exists"));
239
240        backend.delete("key1").await.expect("Failed to delete");
241        assert!(!backend
242            .exists("key1")
243            .await
244            .expect("Failed to check exists"));
245    }
246
247    #[tokio::test]
248    async fn test_inmemory_backend_ttl_expiration() {
249        let backend = InMemoryBackend::new();
250
251        backend
252            .set("key1", b"value1".to_vec(), Some(Duration::from_millis(100)))
253            .await
254            .expect("Failed to set");
255
256        // Should be present immediately
257        assert!(backend.get("key1").await.expect("Failed to get").is_some());
258
259        // Wait for expiration
260        tokio::time::sleep(Duration::from_millis(150)).await;
261
262        // Should be expired now
263        assert!(backend.get("key1").await.expect("Failed to get").is_none());
264    }
265
266    #[tokio::test]
267    async fn test_inmemory_backend_mget() {
268        let backend = InMemoryBackend::new();
269
270        backend
271            .set("key1", b"value1".to_vec(), None)
272            .await
273            .expect("Failed to set");
274        backend
275            .set("key2", b"value2".to_vec(), None)
276            .await
277            .expect("Failed to set");
278
279        let results = backend
280            .mget(&["key1", "key2", "key3"])
281            .await
282            .expect("Failed to mget");
283
284        assert_eq!(results.len(), 3);
285        assert_eq!(results[0], Some(b"value1".to_vec()));
286        assert_eq!(results[1], Some(b"value2".to_vec()));
287        assert_eq!(results[2], None);
288    }
289
290    #[tokio::test]
291    async fn test_inmemory_backend_mdelete() {
292        let backend = InMemoryBackend::new();
293
294        backend
295            .set("key1", b"value1".to_vec(), None)
296            .await
297            .expect("Failed to set");
298        backend
299            .set("key2", b"value2".to_vec(), None)
300            .await
301            .expect("Failed to set");
302        backend
303            .set("key3", b"value3".to_vec(), None)
304            .await
305            .expect("Failed to set");
306
307        assert_eq!(backend.len().await, 3);
308
309        backend
310            .mdelete(&["key1", "key2"])
311            .await
312            .expect("Failed to mdelete");
313
314        assert_eq!(backend.len().await, 1);
315        assert!(backend.get("key3").await.expect("Failed to get").is_some());
316    }
317
318    #[tokio::test]
319    async fn test_inmemory_backend_clear_all() {
320        let backend = InMemoryBackend::new();
321
322        backend
323            .set("key1", b"value1".to_vec(), None)
324            .await
325            .expect("Failed to set");
326        backend
327            .set("key2", b"value2".to_vec(), None)
328            .await
329            .expect("Failed to set");
330
331        assert_eq!(backend.len().await, 2);
332
333        backend.clear_all().await.expect("Failed to clear");
334
335        assert_eq!(backend.len().await, 0);
336    }
337
338    #[tokio::test]
339    async fn test_inmemory_backend_stats() {
340        let backend = InMemoryBackend::new();
341
342        backend
343            .set("key1", b"value_with_data".to_vec(), None)
344            .await
345            .expect("Failed to set");
346        backend
347            .set("key2", b"data".to_vec(), None)
348            .await
349            .expect("Failed to set");
350
351        let stats = backend.stats().await;
352        assert_eq!(stats.total_entries, 2);
353        assert_eq!(stats.expired_entries, 0);
354        assert!(stats.total_bytes > 0);
355    }
356
357    #[tokio::test]
358    async fn test_inmemory_backend_clone() {
359        let backend1 = InMemoryBackend::new();
360        backend1
361            .set("key", b"value".to_vec(), None)
362            .await
363            .expect("Failed to set");
364
365        let backend2 = backend1.clone();
366
367        // Both backends share the same store
368        let value = backend2.store.get("key").map(|e| e.data.clone());
369        assert_eq!(value, Some(b"value".to_vec()));
370    }
371
372    #[tokio::test]
373    async fn test_inmemory_backend_thread_safe() {
374        use std::sync::Arc;
375
376        let backend = Arc::new(InMemoryBackend::new());
377        let mut handles = vec![];
378
379        for i in 0..10 {
380            let backend_clone = Arc::clone(&backend);
381            let handle = tokio::spawn(async move {
382                let b = (*backend_clone).clone();
383                let key = format!("key_{}", i);
384                let value = format!("value_{}", i);
385                b.set(&key, value.into_bytes(), None)
386                    .await
387                    .expect("Failed to set");
388            });
389            handles.push(handle);
390        }
391
392        for handle in handles {
393            handle.await.expect("Task failed");
394        }
395
396        assert!(backend.clone().len().await >= 10);
397    }
398}