1use crate::error::CacheResult;
4use crate::traits::CacheStore;
5use serde::{Serialize, de::DeserializeOwned};
6use std::collections::HashMap;
7use std::sync::{Arc, Mutex as StdMutex};
8use std::time::Duration;
9use tokio::sync::Mutex as AsyncMutex;
10
11pub struct CacheManager<S: CacheStore> {
13 store: Arc<S>,
14 inflight: StdMutex<HashMap<String, Arc<AsyncMutex<()>>>>,
22}
23
24impl<S: CacheStore> CacheManager<S> {
25 pub fn new(store: S) -> Self {
27 Self {
28 store: Arc::new(store),
29 inflight: StdMutex::new(HashMap::new()),
30 }
31 }
32
33 pub async fn get<T: DeserializeOwned>(&self, key: &str) -> CacheResult<Option<T>> {
35 if let Some(json) = self.store.get_json(key).await? {
36 let value: T = serde_json::from_str(&json)
37 .map_err(|e| crate::error::CacheError::Deserialization(e.to_string()))?;
38 Ok(Some(value))
39 } else {
40 Ok(None)
41 }
42 }
43
44 pub async fn set<T: Serialize>(
46 &self,
47 key: &str,
48 value: &T,
49 ttl: Option<Duration>,
50 ) -> CacheResult<()> {
51 let json = serde_json::to_string(value)
52 .map_err(|e| crate::error::CacheError::Serialization(e.to_string()))?;
53 self.store.set_json(key, json, ttl).await
54 }
55
56 pub async fn get_or_set<T, F, Fut>(
68 &self,
69 key: &str,
70 ttl: Option<Duration>,
71 factory: F,
72 ) -> CacheResult<T>
73 where
74 T: Serialize + DeserializeOwned,
75 F: FnOnce() -> Fut,
76 Fut: std::future::Future<Output = CacheResult<T>>,
77 {
78 if let Some(value) = self.get(key).await? {
80 return Ok(value);
81 }
82
83 let key_lock = {
85 let mut map = self.inflight.lock().unwrap();
86 map.entry(key.to_string())
87 .or_insert_with(|| Arc::new(AsyncMutex::new(())))
88 .clone()
89 };
90 let guard = key_lock.lock().await;
91
92 let result = if let Some(value) = self.get(key).await? {
95 Ok(value)
96 } else {
97 match factory().await {
98 Ok(value) => {
99 self.set(key, &value, ttl).await?;
100 Ok(value)
101 }
102 Err(e) => Err(e),
103 }
104 };
105 drop(guard);
106
107 {
110 let mut map = self.inflight.lock().unwrap();
111 if let Some(existing) = map.get(key)
112 && Arc::ptr_eq(existing, &key_lock)
113 && Arc::strong_count(&key_lock) == 2
114 {
115 map.remove(key);
118 }
119 }
120
121 result
122 }
123
124 pub async fn delete(&self, key: &str) -> CacheResult<()> {
126 self.store.delete(key).await
127 }
128
129 pub async fn exists(&self, key: &str) -> CacheResult<bool> {
131 self.store.exists(key).await
132 }
133
134 pub async fn clear(&self) -> CacheResult<()> {
136 self.store.clear().await
137 }
138
139 pub async fn ttl(&self, key: &str) -> CacheResult<Option<Duration>> {
141 self.store.ttl(key).await
142 }
143
144 pub async fn expire(&self, key: &str, ttl: Duration) -> CacheResult<()> {
146 self.store.expire(key, ttl).await
147 }
148
149 pub fn namespace(&self, prefix: &str) -> NamespacedCache<S> {
151 NamespacedCache {
152 store: self.store.clone(),
153 prefix: prefix.to_string(),
154 }
155 }
156}
157
158pub struct NamespacedCache<S: CacheStore> {
160 store: Arc<S>,
161 prefix: String,
162}
163
164impl<S: CacheStore> NamespacedCache<S> {
165 fn build_key(&self, key: &str) -> String {
166 format!("{}:{}", self.prefix, key)
167 }
168
169 pub async fn get<T: DeserializeOwned>(&self, key: &str) -> CacheResult<Option<T>> {
171 let key = self.build_key(key);
172 if let Some(json) = self.store.get_json(&key).await? {
173 let value: T = serde_json::from_str(&json)
174 .map_err(|e| crate::error::CacheError::Deserialization(e.to_string()))?;
175 Ok(Some(value))
176 } else {
177 Ok(None)
178 }
179 }
180
181 pub async fn set<T: Serialize>(
183 &self,
184 key: &str,
185 value: &T,
186 ttl: Option<Duration>,
187 ) -> CacheResult<()> {
188 let key = self.build_key(key);
189 let json = serde_json::to_string(value)
190 .map_err(|e| crate::error::CacheError::Serialization(e.to_string()))?;
191 self.store.set_json(&key, json, ttl).await
192 }
193
194 pub async fn delete(&self, key: &str) -> CacheResult<()> {
196 let key = self.build_key(key);
197 self.store.delete(&key).await
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::tiered::InMemoryCache;
205 use std::sync::atomic::{AtomicUsize, Ordering};
206
207 #[tokio::test]
208 async fn test_get_or_set_single_flight_coalesces_factory() {
209 let manager = CacheManager::new(InMemoryCache::new());
210 let calls = Arc::new(AtomicUsize::new(0));
211
212 let make_fut = |calls: Arc<AtomicUsize>| {
217 let manager = &manager;
218 async move {
219 manager
220 .get_or_set::<i64, _, _>("hot-key", None, || {
221 let calls = calls.clone();
222 async move {
223 tokio::task::yield_now().await;
224 calls.fetch_add(1, Ordering::SeqCst);
225 Ok(42)
226 }
227 })
228 .await
229 .unwrap()
230 }
231 };
232
233 let futs = (0..16).map(|_| make_fut(calls.clone()));
234 let results = futures::future::join_all(futs).await;
235
236 assert!(results.iter().all(|&v| v == 42));
237 assert_eq!(
238 calls.load(Ordering::SeqCst),
239 1,
240 "factory should run exactly once under single-flight"
241 );
242 }
243
244 #[tokio::test]
245 async fn test_get_or_set_returns_cached_value_without_calling_factory() {
246 let manager = CacheManager::new(InMemoryCache::new());
247 manager.set("k", &7_i64, None).await.unwrap();
248
249 let calls = Arc::new(AtomicUsize::new(0));
250 let calls_c = calls.clone();
251 let value: i64 = manager
252 .get_or_set("k", None, || {
253 let calls = calls_c.clone();
254 async move {
255 calls.fetch_add(1, Ordering::SeqCst);
256 Ok(99)
257 }
258 })
259 .await
260 .unwrap();
261
262 assert_eq!(value, 7);
263 assert_eq!(calls.load(Ordering::SeqCst), 0);
264 }
265
266 #[tokio::test]
267 async fn test_get_or_set_inflight_map_cleaned_up() {
268 let manager = CacheManager::new(InMemoryCache::new());
269 let _: i64 = manager
270 .get_or_set("k", None, || async { Ok(1) })
271 .await
272 .unwrap();
273
274 assert!(manager.inflight.lock().unwrap().is_empty());
276 }
277
278 #[test]
279 fn test_namespace_build_key() {
280 struct MockStore;
281
282 #[async_trait::async_trait]
283 impl CacheStore for MockStore {
284 async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
285 Ok(None)
286 }
287 async fn set_json(
288 &self,
289 _key: &str,
290 _value: String,
291 _ttl: Option<Duration>,
292 ) -> CacheResult<()> {
293 Ok(())
294 }
295 async fn delete(&self, _key: &str) -> CacheResult<()> {
296 Ok(())
297 }
298 async fn exists(&self, _key: &str) -> CacheResult<bool> {
299 Ok(false)
300 }
301 async fn clear(&self) -> CacheResult<()> {
302 Ok(())
303 }
304 async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
305 Ok(None)
306 }
307 async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
308 Ok(())
309 }
310 async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
311 Ok(0)
312 }
313 async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
314 Ok(0)
315 }
316 }
317
318 let namespaced = NamespacedCache {
319 store: Arc::new(MockStore),
320 prefix: "users".to_string(),
321 };
322
323 assert_eq!(namespaced.build_key("123"), "users:123");
324 }
325
326 #[test]
327 fn test_namespace_build_key_empty() {
328 struct MockStore;
329
330 #[async_trait::async_trait]
331 impl CacheStore for MockStore {
332 async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
333 Ok(None)
334 }
335 async fn set_json(
336 &self,
337 _key: &str,
338 _value: String,
339 _ttl: Option<Duration>,
340 ) -> CacheResult<()> {
341 Ok(())
342 }
343 async fn delete(&self, _key: &str) -> CacheResult<()> {
344 Ok(())
345 }
346 async fn exists(&self, _key: &str) -> CacheResult<bool> {
347 Ok(false)
348 }
349 async fn clear(&self) -> CacheResult<()> {
350 Ok(())
351 }
352 async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
353 Ok(None)
354 }
355 async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
356 Ok(())
357 }
358 async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
359 Ok(0)
360 }
361 async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
362 Ok(0)
363 }
364 }
365
366 let namespaced = NamespacedCache {
367 store: Arc::new(MockStore),
368 prefix: "app".to_string(),
369 };
370
371 assert_eq!(namespaced.build_key(""), "app:");
372 }
373
374 #[test]
375 fn test_namespace_build_key_with_colons() {
376 struct MockStore;
377
378 #[async_trait::async_trait]
379 impl CacheStore for MockStore {
380 async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
381 Ok(None)
382 }
383 async fn set_json(
384 &self,
385 _key: &str,
386 _value: String,
387 _ttl: Option<Duration>,
388 ) -> CacheResult<()> {
389 Ok(())
390 }
391 async fn delete(&self, _key: &str) -> CacheResult<()> {
392 Ok(())
393 }
394 async fn exists(&self, _key: &str) -> CacheResult<bool> {
395 Ok(false)
396 }
397 async fn clear(&self) -> CacheResult<()> {
398 Ok(())
399 }
400 async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
401 Ok(None)
402 }
403 async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
404 Ok(())
405 }
406 async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
407 Ok(0)
408 }
409 async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
410 Ok(0)
411 }
412 }
413
414 let namespaced = NamespacedCache {
415 store: Arc::new(MockStore),
416 prefix: "app".to_string(),
417 };
418
419 assert_eq!(namespaced.build_key("user:123"), "app:user:123");
420 }
421
422 #[test]
423 fn test_namespace_multiple_prefixes() {
424 struct MockStore;
425
426 #[async_trait::async_trait]
427 impl CacheStore for MockStore {
428 async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
429 Ok(None)
430 }
431 async fn set_json(
432 &self,
433 _key: &str,
434 _value: String,
435 _ttl: Option<Duration>,
436 ) -> CacheResult<()> {
437 Ok(())
438 }
439 async fn delete(&self, _key: &str) -> CacheResult<()> {
440 Ok(())
441 }
442 async fn exists(&self, _key: &str) -> CacheResult<bool> {
443 Ok(false)
444 }
445 async fn clear(&self) -> CacheResult<()> {
446 Ok(())
447 }
448 async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
449 Ok(None)
450 }
451 async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
452 Ok(())
453 }
454 async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
455 Ok(0)
456 }
457 async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
458 Ok(0)
459 }
460 }
461
462 let ns1 = NamespacedCache {
463 store: Arc::new(MockStore),
464 prefix: "app1".to_string(),
465 };
466
467 let ns2 = NamespacedCache {
468 store: Arc::new(MockStore),
469 prefix: "app2".to_string(),
470 };
471
472 assert_eq!(ns1.build_key("key"), "app1:key");
473 assert_eq!(ns2.build_key("key"), "app2:key");
474 assert_ne!(ns1.build_key("key"), ns2.build_key("key"));
475 }
476}