multi_tier_cache/backends/
dashmap_cache.rs1use anyhow::Result;
7use dashmap::DashMap;
8use serde_json;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12use tracing::{debug, info};
13
14#[derive(Debug, Clone)]
16struct CacheEntry {
17 value: serde_json::Value,
18 expires_at: Option<Instant>,
19}
20
21impl CacheEntry {
22 fn new(value: serde_json::Value, ttl: Duration) -> Self {
23 Self {
24 value,
25 expires_at: Some(Instant::now() + ttl),
26 }
27 }
28
29 fn is_expired(&self) -> bool {
30 self.expires_at
31 .is_some_and(|expires_at| Instant::now() > expires_at)
32 }
33}
34
35pub struct DashMapCache {
72 map: Arc<DashMap<String, CacheEntry>>,
74 hits: Arc<AtomicU64>,
76 misses: Arc<AtomicU64>,
78 sets: Arc<AtomicU64>,
80}
81
82impl DashMapCache {
83 pub fn new() -> Self {
85 info!("Initializing DashMap Cache (concurrent HashMap)");
86
87 Self {
88 map: Arc::new(DashMap::new()),
89 hits: Arc::new(AtomicU64::new(0)),
90 misses: Arc::new(AtomicU64::new(0)),
91 sets: Arc::new(AtomicU64::new(0)),
92 }
93 }
94
95 pub fn cleanup_expired(&self) -> usize {
100 let mut removed = 0;
101 self.map.retain(|_, entry| {
102 if entry.is_expired() {
103 removed += 1;
104 false } else {
106 true }
108 });
109 if removed > 0 {
110 debug!(count = removed, "[DashMap] Cleaned up expired entries");
111 }
112 removed
113 }
114
115 #[must_use]
117 pub fn len(&self) -> usize {
118 self.map.len()
119 }
120
121 #[must_use]
123 pub fn is_empty(&self) -> bool {
124 self.map.is_empty()
125 }
126}
127
128impl Default for DashMapCache {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134use crate::traits::CacheBackend;
137use async_trait::async_trait;
138
139#[async_trait]
141impl CacheBackend for DashMapCache {
142 async fn get(&self, key: &str) -> Option<serde_json::Value> {
143 if let Some(entry) = self.map.get(key) {
144 if entry.is_expired() {
145 drop(entry); self.map.remove(key);
148 self.misses.fetch_add(1, Ordering::Relaxed);
149 None
150 } else {
151 self.hits.fetch_add(1, Ordering::Relaxed);
152 Some(entry.value.clone())
153 }
154 } else {
155 self.misses.fetch_add(1, Ordering::Relaxed);
156 None
157 }
158 }
159
160 async fn set_with_ttl(&self, key: &str, value: serde_json::Value, ttl: Duration) -> Result<()> {
161 let entry = CacheEntry::new(value, ttl);
162 self.map.insert(key.to_string(), entry);
163 self.sets.fetch_add(1, Ordering::Relaxed);
164 debug!(key = %key, ttl_secs = %ttl.as_secs(), "[DashMap] Cached key with TTL");
165 Ok(())
166 }
167
168 async fn remove(&self, key: &str) -> Result<()> {
169 self.map.remove(key);
170 Ok(())
171 }
172
173 async fn health_check(&self) -> bool {
174 let test_key = "health_check_dashmap";
175 let test_value = serde_json::json!({"test": true});
176
177 match self
178 .set_with_ttl(test_key, test_value.clone(), Duration::from_secs(60))
179 .await
180 {
181 Ok(()) => match self.get(test_key).await {
182 Some(retrieved) => {
183 let _ = self.remove(test_key).await;
184 retrieved == test_value
185 }
186 None => false,
187 },
188 Err(_) => false,
189 }
190 }
191
192 fn name(&self) -> &'static str {
193 "DashMap"
194 }
195}