crates_docs/cache/
memory.rs1use std::sync::Mutex;
6use std::time::{Duration, Instant};
7
8struct 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
26pub struct MemoryCache {
30 cache: Mutex<lru::LruCache<String, CacheEntry>>,
32}
33
34impl MemoryCache {
35 #[must_use]
40 pub fn new(max_size: usize) -> Self {
41 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 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 fn cleanup_expired(cache: &mut lru::LruCache<String, CacheEntry>) {
64 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 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 Self::cleanup_expired(&mut cache);
90
91 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 Self::cleanup_expired(&mut cache);
111
112 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 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 cache.delete("key1").await.expect("delete should succeed");
156 assert_eq!(cache.get("key1").await, None);
157
158 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 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 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 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 let _ = cache.get("key1").await;
203
204 cache
206 .set("key3".to_string(), "value3".to_string(), None)
207 .await
208 .expect("set should succeed");
209
210 assert_eq!(cache.get("key1").await, Some("value1".to_string()));
212 assert_eq!(cache.get("key2").await, None);
214 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}