Skip to main content

crates_docs/cache/
memory.rs

1//! Memory cache implementation
2//!
3//! Memory cache using LRU (Least Recently Used) eviction strategy.
4
5use std::sync::Mutex;
6use std::time::{Duration, Instant};
7
8/// Cache entry
9struct CacheEntry {
10    value: String,
11    expires_at: Option<Instant>,
12}
13
14impl CacheEntry {
15    fn new(value: String, ttl: Option<Duration>) -> Self {
16        let expires_at = ttl.map(|duration| Instant::now() + duration);
17        Self { value, expires_at }
18    }
19
20    fn is_expired(&self) -> bool {
21        self.expires_at
22            .is_some_and(|expiry| expiry <= Instant::now())
23    }
24}
25
26/// Memory cache implementation
27///
28/// Uses LRU eviction strategy, removes least recently used entries when cache is full.
29pub struct MemoryCache {
30    /// LRU cache, using Mutex for thread safety
31    cache: Mutex<lru::LruCache<String, CacheEntry>>,
32}
33
34impl MemoryCache {
35    /// Create a new memory cache
36    ///
37    /// # Arguments
38    /// * `max_size` - Maximum number of cache entries
39    #[must_use]
40    pub fn new(max_size: usize) -> Self {
41        // Use non-zero type to ensure cache size is at least 1
42        let cap =
43            std::num::NonZeroUsize::new(max_size.max(1)).expect("cache size must be at least 1");
44        Self {
45            cache: Mutex::new(lru::LruCache::new(cap)),
46        }
47    }
48
49    /// Safely acquire the cache lock, handling lock poisoning gracefully.
50    ///
51    /// When a thread panics while holding the lock, the lock becomes "poisoned".
52    /// Instead of panicking, we recover the data and log a warning.
53    fn acquire_lock(&self) -> std::sync::MutexGuard<'_, lru::LruCache<String, CacheEntry>> {
54        self.cache.lock().unwrap_or_else(|poisoned| {
55            tracing::warn!(
56                "Cache lock was poisoned, recovering data. This indicates a previous panic while holding the lock."
57            );
58            poisoned.into_inner()
59        })
60    }
61
62    /// Clean up expired entries
63    fn cleanup_expired(cache: &mut lru::LruCache<String, CacheEntry>) {
64        // Collect expired keys
65        let expired_keys: Vec<String> = cache
66            .iter()
67            .filter_map(|(k, v)| {
68                if v.is_expired() {
69                    Some(k.clone())
70                } else {
71                    None
72                }
73            })
74            .collect();
75
76        // Remove expired entries
77        for key in expired_keys {
78            cache.pop(&key);
79        }
80    }
81}
82
83#[async_trait::async_trait]
84impl super::Cache for MemoryCache {
85    async fn get(&self, key: &str) -> Option<String> {
86        let mut cache = self.acquire_lock();
87
88        // First check and clean up expired entries
89        Self::cleanup_expired(&mut cache);
90
91        // Get value (LRU automatically moves it to most recently used position)
92        cache.get(key).and_then(|entry| {
93            if entry.is_expired() {
94                None
95            } else {
96                Some(entry.value.clone())
97            }
98        })
99    }
100
101    async fn set(
102        &self,
103        key: String,
104        value: String,
105        ttl: Option<Duration>,
106    ) -> crate::error::Result<()> {
107        let mut cache = self.acquire_lock();
108
109        // Clean up expired entries
110        Self::cleanup_expired(&mut cache);
111
112        // LRU automatically handles eviction
113        let entry = CacheEntry::new(value, ttl);
114        cache.put(key, entry);
115        Ok(())
116    }
117
118    async fn delete(&self, key: &str) -> crate::error::Result<()> {
119        let mut cache = self.acquire_lock();
120        cache.pop(key);
121        Ok(())
122    }
123
124    async fn clear(&self) -> crate::error::Result<()> {
125        let mut cache = self.acquire_lock();
126        cache.clear();
127        Ok(())
128    }
129
130    async fn exists(&self, key: &str) -> bool {
131        let mut cache = self.acquire_lock();
132        Self::cleanup_expired(&mut cache);
133        cache.contains(key)
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::cache::Cache;
141    use tokio::time::sleep;
142
143    #[tokio::test]
144    async fn test_memory_cache_basic() {
145        let cache = MemoryCache::new(10);
146
147        // 测试设置和获取
148        cache
149            .set("key1".to_string(), "value1".to_string(), None)
150            .await
151            .expect("set should succeed");
152        assert_eq!(cache.get("key1").await, Some("value1".to_string()));
153
154        // 测试删除
155        cache.delete("key1").await.expect("delete should succeed");
156        assert_eq!(cache.get("key1").await, None);
157
158        // 测试清空
159        cache
160            .set("key2".to_string(), "value2".to_string(), None)
161            .await
162            .expect("set should succeed");
163        cache.clear().await.expect("clear should succeed");
164        assert_eq!(cache.get("key2").await, None);
165    }
166
167    #[tokio::test]
168    async fn test_memory_cache_ttl() {
169        let cache = MemoryCache::new(10);
170
171        // 测试带 TTL 的缓存
172        cache
173            .set(
174                "key1".to_string(),
175                "value1".to_string(),
176                Some(Duration::from_millis(100)),
177            )
178            .await
179            .expect("set should succeed");
180        assert_eq!(cache.get("key1").await, Some("value1".to_string()));
181
182        // 等待过期
183        sleep(Duration::from_millis(150)).await;
184        assert_eq!(cache.get("key1").await, None);
185    }
186
187    #[tokio::test]
188    async fn test_memory_cache_lru_eviction() {
189        let cache = MemoryCache::new(2);
190
191        // 填满缓存
192        cache
193            .set("key1".to_string(), "value1".to_string(), None)
194            .await
195            .expect("set should succeed");
196        cache
197            .set("key2".to_string(), "value2".to_string(), None)
198            .await
199            .expect("set should succeed");
200
201        // 访问 key1,使其成为最近使用
202        let _ = cache.get("key1").await;
203
204        // 添加第三个条目,应该淘汰 key2(最少使用)
205        cache
206            .set("key3".to_string(), "value3".to_string(), None)
207            .await
208            .expect("set should succeed");
209
210        // key1 应该还在(因为刚被访问)
211        assert_eq!(cache.get("key1").await, Some("value1".to_string()));
212        // key2 应该被淘汰
213        assert_eq!(cache.get("key2").await, None);
214        // key3 应该存在
215        assert_eq!(cache.get("key3").await, Some("value3".to_string()));
216    }
217
218    #[tokio::test]
219    async fn test_memory_cache_exists() {
220        let cache = MemoryCache::new(10);
221
222        cache
223            .set("key1".to_string(), "value1".to_string(), None)
224            .await
225            .expect("set should succeed");
226        assert!(cache.exists("key1").await);
227        assert!(!cache.exists("key2").await);
228    }
229}