1use crate::types::EKey;
4use dashmap::DashMap;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicUsize, Ordering};
7use std::time::Instant;
8use tracing::debug;
9
10pub struct LockFreeCache {
14 map: Arc<DashMap<EKey, CacheEntry>>,
16 max_size: usize,
18 current_size: Arc<AtomicUsize>,
20 hits: Arc<AtomicUsize>,
22 misses: Arc<AtomicUsize>,
24}
25
26#[derive(Clone)]
28struct CacheEntry {
29 data: Arc<Vec<u8>>,
31 size: usize,
33 last_access: Instant,
35 access_count: usize,
37}
38
39impl LockFreeCache {
40 pub fn new(max_size_bytes: usize) -> Self {
42 Self {
43 map: Arc::new(DashMap::with_capacity(1024)),
44 max_size: max_size_bytes,
45 current_size: Arc::new(AtomicUsize::new(0)),
46 hits: Arc::new(AtomicUsize::new(0)),
47 misses: Arc::new(AtomicUsize::new(0)),
48 }
49 }
50
51 pub fn get(&self, key: &EKey) -> Option<Arc<Vec<u8>>> {
53 if let Some(mut entry) = self.map.get_mut(key) {
54 entry.last_access = Instant::now();
56 entry.access_count += 1;
57
58 self.hits.fetch_add(1, Ordering::Relaxed);
59 Some(Arc::clone(&entry.data))
60 } else {
61 self.misses.fetch_add(1, Ordering::Relaxed);
62 None
63 }
64 }
65
66 pub fn put(&self, key: EKey, data: Arc<Vec<u8>>) {
68 let size = data.len();
69
70 if self.current_size.load(Ordering::Relaxed) + size > self.max_size {
72 self.evict_until_space_available(size);
73 }
74
75 let entry = CacheEntry {
76 data,
77 size,
78 last_access: Instant::now(),
79 access_count: 1,
80 };
81
82 if let Some(old_entry) = self.map.insert(key, entry) {
84 self.current_size
86 .fetch_sub(old_entry.size, Ordering::Relaxed);
87 }
88
89 self.current_size.fetch_add(size, Ordering::Relaxed);
91 }
92
93 fn evict_until_space_available(&self, needed_space: usize) {
95 let target_size = self.max_size.saturating_sub(needed_space);
96
97 let mut candidates: Vec<(EKey, f64, usize)> = self
99 .map
100 .iter()
101 .map(|entry| {
102 let key = *entry.key();
103 let score = self.calculate_eviction_score(&entry);
104 let size = entry.size;
105 (key, score, size)
106 })
107 .collect();
108
109 candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
111
112 for (key, _score, size) in candidates {
114 if self.current_size.load(Ordering::Relaxed) <= target_size {
115 break;
116 }
117
118 if self.map.remove(&key).is_some() {
119 self.current_size.fetch_sub(size, Ordering::Relaxed);
120 debug!("Evicted {} from cache (size: {})", key, size);
121 }
122 }
123 }
124
125 fn calculate_eviction_score(&self, entry: &CacheEntry) -> f64 {
128 let age = entry.last_access.elapsed().as_secs_f64();
129 let frequency = entry.access_count as f64;
130
131 frequency / (1.0 + age)
134 }
135
136 pub fn clear(&self) {
138 self.map.clear();
139 self.current_size.store(0, Ordering::Relaxed);
140 debug!("Cache cleared");
141 }
142
143 pub fn stats(&self) -> CacheStats {
145 let hits = self.hits.load(Ordering::Relaxed);
146 let misses = self.misses.load(Ordering::Relaxed);
147 let total_requests = hits + misses;
148
149 CacheStats {
150 size: self.current_size.load(Ordering::Relaxed),
151 max_size: self.max_size,
152 entry_count: self.map.len(),
153 hits,
154 misses,
155 hit_rate: if total_requests > 0 {
156 (hits as f64) / (total_requests as f64)
157 } else {
158 0.0
159 },
160 }
161 }
162
163 pub fn contains(&self, key: &EKey) -> bool {
165 self.map.contains_key(key)
166 }
167
168 pub fn current_size(&self) -> usize {
170 self.current_size.load(Ordering::Relaxed)
171 }
172
173 pub fn reserve(&self, additional: usize) {
175 debug!("Cache reserve hint: {} additional entries", additional);
178 }
179}
180
181#[derive(Debug, Clone)]
183pub struct CacheStats {
184 pub size: usize,
186 pub max_size: usize,
188 pub entry_count: usize,
190 pub hits: usize,
192 pub misses: usize,
194 pub hit_rate: f64,
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_basic_cache_operations() {
204 let cache = LockFreeCache::new(1024 * 1024); let key1 = EKey::new([
208 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab,
209 0xcd, 0xef,
210 ]);
211 let data1 = Arc::new(vec![1, 2, 3, 4, 5]);
212
213 cache.put(key1, Arc::clone(&data1));
215 let retrieved = cache.get(&key1).unwrap();
216 assert!(Arc::ptr_eq(&data1, &retrieved));
217
218 let stats = cache.stats();
220 assert_eq!(stats.hits, 1);
221 assert_eq!(stats.misses, 0);
222 assert_eq!(stats.entry_count, 1);
223 }
224
225 #[test]
226 fn test_cache_eviction() {
227 let cache = LockFreeCache::new(100); for i in 0..20 {
231 let mut key_bytes = [0u8; 16];
232 key_bytes[0] = i as u8;
233 let key = EKey::new(key_bytes);
234 let data = Arc::new(vec![i as u8; 10]); cache.put(key, data);
236 }
237
238 assert!(cache.current_size() <= 100);
240 assert!(cache.map.len() < 20);
241 }
242
243 #[test]
244 fn test_concurrent_access() {
245 use std::thread;
246
247 let cache = Arc::new(LockFreeCache::new(10 * 1024 * 1024)); let mut handles = vec![];
249
250 for i in 0..10 {
252 let cache_clone = Arc::clone(&cache);
253 let handle = thread::spawn(move || {
254 for j in 0..100 {
255 let mut key_bytes = [0u8; 16];
256 let val = (i * 100 + j) as u16;
257 key_bytes[0] = (val >> 8) as u8;
258 key_bytes[1] = (val & 0xff) as u8;
259 let key = EKey::new(key_bytes);
260 let data = Arc::new(vec![i as u8; 100]);
261
262 cache_clone.put(key, Arc::clone(&data));
264 cache_clone.get(&key);
265 }
266 });
267 handles.push(handle);
268 }
269
270 for handle in handles {
272 handle.join().unwrap();
273 }
274
275 let stats = cache.stats();
277 assert!(stats.entry_count > 0);
278 assert!(stats.hits > 0);
279 }
280
281 #[test]
282 fn test_zero_copy_behavior() {
283 let cache = LockFreeCache::new(1024 * 1024);
284
285 let key = EKey::new([
286 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab,
287 0xcd, 0xef,
288 ]);
289 let data = Arc::new(vec![1, 2, 3, 4, 5]);
290
291 cache.put(key, Arc::clone(&data));
292
293 let retrieved1 = cache.get(&key).unwrap();
295 let retrieved2 = cache.get(&key).unwrap();
296
297 assert!(Arc::ptr_eq(&retrieved1, &retrieved2));
299 assert!(Arc::ptr_eq(&data, &retrieved1));
300 }
301
302 #[test]
303 fn test_frequency_based_eviction() {
304 let cache = LockFreeCache::new(150); let key1 = EKey::new([0x11; 16]);
308 let key2 = EKey::new([0x22; 16]);
309 let key3 = EKey::new([0x33; 16]);
310
311 cache.put(key1, Arc::new(vec![1; 50]));
312 cache.put(key2, Arc::new(vec![2; 50]));
313
314 for _ in 0..5 {
316 cache.get(&key1);
317 }
318
319 cache.put(key3, Arc::new(vec![3; 50]));
321
322 assert!(cache.contains(&key1));
324 assert!(cache.contains(&key3));
326 }
327}