1use std::collections::HashMap;
37use std::collections::VecDeque;
38use std::sync::atomic::{AtomicU64, Ordering};
39use std::sync::Arc;
40
41#[derive(Debug, Clone, Default)]
47pub struct L1CacheStats {
48 pub hits: u64,
50 pub misses: u64,
52 pub entry_count: usize,
54 pub evict_count: u64,
56}
57
58impl L1CacheStats {
59 pub fn total_lookups(&self) -> u64 {
61 self.hits + self.misses
62 }
63
64 pub fn hit_rate(&self) -> f64 {
66 let total = self.total_lookups();
67 if total == 0 {
68 0.0
69 } else {
70 self.hits as f64 / total as f64
71 }
72 }
73}
74
75pub struct L1Cache<T> {
88 data: HashMap<i64, Arc<T>>,
90 lru_order: VecDeque<i64>,
92 capacity: usize,
94 hits: AtomicU64,
96 misses: AtomicU64,
98 evicts: AtomicU64,
100}
101
102impl<T> L1Cache<T> {
103 pub fn new(capacity: usize) -> Self {
107 Self {
108 data: HashMap::with_capacity(capacity),
109 lru_order: VecDeque::with_capacity(capacity),
110 capacity: capacity.max(1),
111 hits: AtomicU64::new(0),
112 misses: AtomicU64::new(0),
113 evicts: AtomicU64::new(0),
114 }
115 }
116
117 pub fn capacity(&self) -> usize {
119 self.capacity
120 }
121
122 pub fn put(&mut self, key: i64, value: Arc<T>) {
127 if let std::collections::hash_map::Entry::Occupied(mut e) = self.data.entry(key) {
129 e.insert(value);
130 self.touch_lru(key);
131 return;
132 }
133
134 if self.data.len() >= self.capacity {
136 if let Some(victim) = self.lru_order.pop_front() {
137 self.data.remove(&victim);
138 self.evicts.fetch_add(1, Ordering::Relaxed);
139 }
140 }
141
142 self.data.insert(key, value);
143 self.lru_order.push_back(key);
144 }
145
146 pub fn get(&mut self, key: &i64) -> Option<Arc<T>> {
150 if let Some(value) = self.data.get(key).map(Arc::clone) {
151 self.hits.fetch_add(1, Ordering::Relaxed);
152 self.touch_lru(*key);
153 Some(value)
154 } else {
155 self.misses.fetch_add(1, Ordering::Relaxed);
156 None
157 }
158 }
159
160 pub fn evict(&mut self, key: &i64) {
162 if self.data.remove(key).is_some() {
163 self.lru_order.retain(|k| k != key);
164 }
165 }
166
167 pub fn clear(&mut self) {
169 self.data.clear();
170 self.lru_order.clear();
171 }
172
173 pub fn len(&self) -> usize {
175 self.data.len()
176 }
177
178 pub fn is_empty(&self) -> bool {
180 self.data.is_empty()
181 }
182
183 pub fn stats(&self) -> L1CacheStats {
185 L1CacheStats {
186 hits: self.hits.load(Ordering::Relaxed),
187 misses: self.misses.load(Ordering::Relaxed),
188 entry_count: self.data.len(),
189 evict_count: self.evicts.load(Ordering::Relaxed),
190 }
191 }
192
193 fn touch_lru(&mut self, key: i64) {
195 self.lru_order.retain(|k| *k != key);
196 self.lru_order.push_back(key);
197 }
198}
199
200impl<T> Default for L1Cache<T> {
201 fn default() -> Self {
202 Self::new(1024)
203 }
204}
205
206pub struct L1L2Coordinator<T: Clone> {
217 l1: L1Cache<T>,
219 l2: Option<std::sync::Arc<crate::l2_cache::L2Cache>>,
221}
222
223impl<T: Clone> L1L2Coordinator<T> {
224 pub fn new(l1_capacity: usize) -> Self {
226 Self {
227 l1: L1Cache::new(l1_capacity),
228 l2: None,
229 }
230 }
231
232 pub fn with_l2(mut self, l2: std::sync::Arc<crate::l2_cache::L2Cache>) -> Self {
234 self.l2 = Some(l2);
235 self
236 }
237
238 pub fn get_or_load<F>(&mut self, table: &str, pk: i64, db_loader: F) -> Option<Arc<T>>
246 where
247 F: FnOnce() -> Option<T>,
248 {
249 if let Some(val) = self.l1.get(&pk) {
251 return Some(val);
252 }
253
254 if let Some(l2) = &self.l2 {
256 let l2_key = crate::l2_cache::CacheKey::by_pk(table, pk);
257 if let Some(crate::value::Value::String(s)) = l2.get(&l2_key) {
258 let val = Arc::new(T::clone(&db_loader().unwrap()));
260 let _ = s;
261 self.l1.put(pk, val.clone());
262 return Some(val);
263 }
264 }
265
266 if let Some(val) = db_loader() {
268 let arc_val = Arc::new(val);
269 self.l1.put(pk, arc_val.clone());
270 if let Some(l2) = &self.l2 {
272 let l2_key = crate::l2_cache::CacheKey::by_pk(table, pk);
273 l2.put(
274 &l2_key,
275 crate::value::Value::String(format!("{}", pk)),
276 None,
277 );
278 }
279 return Some(arc_val);
280 }
281
282 None
283 }
284
285 pub fn invalidate(&mut self, pk: i64) {
287 self.l1.evict(&pk);
288 }
289
290 pub fn clear(&mut self) {
292 self.l1.clear();
293 }
294
295 pub fn l1_stats(&self) -> L1CacheStats {
297 self.l1.stats()
298 }
299
300 pub fn l1_mut(&mut self) -> &mut L1Cache<T> {
302 &mut self.l1
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
313 fn test_identity_map_same_ptr() {
314 let mut cache: L1Cache<String> = L1Cache::new(10);
315 cache.put(1, Arc::new("Alice".to_string()));
316
317 let a = cache.get(&1).unwrap();
318 let b = cache.get(&1).unwrap();
319 assert!(
320 Arc::ptr_eq(&a, &b),
321 "Identity Map: same key must return same Arc ptr"
322 );
323 }
324
325 #[test]
326 fn test_identity_map_different_keys_different_ptrs() {
327 let mut cache: L1Cache<String> = L1Cache::new(10);
328 cache.put(1, Arc::new("Alice".to_string()));
329 cache.put(2, Arc::new("Bob".to_string()));
330
331 let a = cache.get(&1).unwrap();
332 let b = cache.get(&2).unwrap();
333 assert!(
334 !Arc::ptr_eq(&a, &b),
335 "Different keys should return different Arc ptrs"
336 );
337 }
338
339 #[test]
342 fn test_lru_eviction() {
343 let mut cache: L1Cache<i32> = L1Cache::new(3);
344 cache.put(1, Arc::new(10));
345 cache.put(2, Arc::new(20));
346 cache.put(3, Arc::new(30));
347 assert_eq!(cache.len(), 3);
348
349 cache.put(4, Arc::new(40));
351 assert_eq!(cache.len(), 3);
352 assert!(cache.get(&1).is_none(), "key=1 should be evicted (LRU)");
353 assert!(cache.get(&4).is_some());
354
355 let stats = cache.stats();
356 assert!(stats.evict_count >= 1, "evict count should be >= 1");
357 }
358
359 #[test]
360 fn test_lru_touch_on_get() {
361 let mut cache: L1Cache<i32> = L1Cache::new(3);
362 cache.put(1, Arc::new(10));
363 cache.put(2, Arc::new(20));
364 cache.put(3, Arc::new(30));
365
366 let _ = cache.get(&1);
368
369 cache.put(4, Arc::new(40));
371 assert!(
372 cache.get(&1).is_some(),
373 "key=1 should still exist (was accessed)"
374 );
375 assert!(
376 cache.get(&2).is_none(),
377 "key=2 should be evicted (LRU after touch)"
378 );
379 }
380
381 #[test]
384 fn test_stats_hits_misses() {
385 let mut cache: L1Cache<String> = L1Cache::new(10);
386 cache.put(1, Arc::new("Alice".to_string()));
387
388 let _ = cache.get(&1); let _ = cache.get(&1); let _ = cache.get(&99); let stats = cache.stats();
393 assert_eq!(stats.hits, 2);
394 assert_eq!(stats.misses, 1);
395 assert_eq!(stats.entry_count, 1);
396 assert_eq!(stats.evict_count, 0);
397 }
398
399 #[test]
400 fn test_stats_hit_rate() {
401 let mut cache: L1Cache<i32> = L1Cache::new(10);
402 cache.put(1, Arc::new(100));
403
404 let _ = cache.get(&1); let _ = cache.get(&2); let _ = cache.get(&1); let stats = cache.stats();
409 assert_eq!(stats.total_lookups(), 3);
410 assert!((stats.hit_rate() - 2.0 / 3.0).abs() < 1e-9);
411 }
412
413 #[test]
416 fn test_session_drop_clears_cache() {
417 let stats;
418 {
419 let mut cache: L1Cache<String> = L1Cache::new(10);
420 cache.put(1, Arc::new("Alice".to_string()));
421 assert_eq!(cache.len(), 1);
422 stats = cache.stats();
423 }
425 assert_eq!(stats.entry_count, 1); }
427
428 #[test]
429 fn test_different_sessions_isolated() {
430 let mut cache_a: L1Cache<String> = L1Cache::new(10);
432 let mut cache_b: L1Cache<String> = L1Cache::new(10);
433
434 cache_a.put(1, Arc::new("from_session_a".to_string()));
435 cache_b.put(1, Arc::new("from_session_b".to_string()));
436
437 let a = cache_a.get(&1).unwrap();
438 let b = cache_b.get(&1).unwrap();
439 assert_eq!(*a, "from_session_a");
440 assert_eq!(*b, "from_session_b");
441 assert!(
442 !Arc::ptr_eq(&a, &b),
443 "Different sessions should have isolated caches"
444 );
445 }
446
447 #[test]
450 fn test_evict_single_key() {
451 let mut cache: L1Cache<String> = L1Cache::new(10);
452 cache.put(1, Arc::new("Alice".to_string()));
453 cache.put(2, Arc::new("Bob".to_string()));
454
455 cache.evict(&1);
456 assert!(cache.get(&1).is_none(), "key=1 should be evicted");
457 assert!(cache.get(&2).is_some(), "key=2 should still exist");
458 }
459
460 #[test]
461 fn test_clear_all() {
462 let mut cache: L1Cache<String> = L1Cache::new(10);
463 cache.put(1, Arc::new("Alice".to_string()));
464 cache.put(2, Arc::new("Bob".to_string()));
465
466 cache.clear();
467 assert!(cache.is_empty());
468 assert_eq!(cache.len(), 0);
469 }
470
471 #[test]
472 fn test_write_operation_evict() {
473 let mut cache: L1Cache<String> = L1Cache::new(10);
474 cache.put(1, Arc::new("Alice".to_string()));
475
476 cache.evict(&1);
478
479 let result = cache.get(&1);
481 assert!(result.is_none(), "After write evict, get should miss");
482
483 let stats = cache.stats();
484 assert_eq!(stats.misses, 1);
485 }
486
487 #[test]
490 fn test_object_consistency_after_update() {
491 let mut cache: L1Cache<String> = L1Cache::new(10);
492 cache.put(1, Arc::new("original".to_string()));
493
494 let a = cache.get(&1).unwrap();
495 assert_eq!(*a, "original");
496
497 cache.put(1, Arc::new("updated".to_string()));
499 let b = cache.get(&1).unwrap();
500 assert_eq!(*b, "updated");
501
502 assert_eq!(*a, "original");
504 assert_eq!(*b, "updated");
506 }
507
508 #[test]
511 fn test_atomic_stats_thread_safe() {
512 use std::sync::Arc;
513 use std::thread;
514
515 let cache = Arc::new(std::sync::Mutex::new(L1Cache::<i32>::new(100)));
516 let mut handles = Vec::new();
517
518 for i in 0..4 {
519 let cache_clone = Arc::clone(&cache);
520 handles.push(thread::spawn(move || {
521 let mut cache = cache_clone.lock().unwrap();
522 cache.put(i, Arc::new(i as i32));
523 let _ = cache.get(&i);
524 }));
525 }
526
527 for h in handles {
528 h.join().unwrap();
529 }
530
531 let cache = cache.lock().unwrap();
532 let stats = cache.stats();
533 assert_eq!(stats.entry_count, 4);
534 assert!(stats.hits >= 4);
535 }
536
537 #[test]
540 fn test_l1_l2_db_query_order() {
541 let mut coord: L1L2Coordinator<String> = L1L2Coordinator::new(10);
542
543 let db_call_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
545 let db_count_clone = Arc::clone(&db_call_count);
546
547 let result = coord.get_or_load("users", 1, || {
548 db_count_clone.fetch_add(1, Ordering::Relaxed);
549 Some("Alice".to_string())
550 });
551 assert_eq!(*result.unwrap(), "Alice");
552 assert_eq!(
553 db_call_count.load(Ordering::Relaxed),
554 1,
555 "DB should be called once"
556 );
557
558 let db_count_clone2 = Arc::clone(&db_call_count);
560 let result2 = coord.get_or_load("users", 1, || {
561 db_count_clone2.fetch_add(1, Ordering::Relaxed);
562 Some("Alice".to_string())
563 });
564 assert_eq!(*result2.unwrap(), "Alice");
565 assert_eq!(
566 db_call_count.load(Ordering::Relaxed),
567 1,
568 "DB should NOT be called again (L1 hit)"
569 );
570 }
571
572 #[test]
573 fn test_l1_l2_db_invalidate_after_write() {
574 let mut coord: L1L2Coordinator<String> = L1L2Coordinator::new(10);
575
576 let result = coord.get_or_load("users", 1, || Some("Alice".to_string()));
578 assert_eq!(*result.unwrap(), "Alice");
579
580 coord.invalidate(1);
582
583 let result2 = coord.get_or_load("users", 1, || Some("Bob".to_string()));
585 assert_eq!(
586 *result2.unwrap(),
587 "Bob",
588 "After invalidate, should reload from DB"
589 );
590 }
591
592 #[test]
595 fn test_capacity_one() {
596 let mut cache: L1Cache<i32> = L1Cache::new(1);
597 cache.put(1, Arc::new(10));
598 cache.put(2, Arc::new(20));
599
600 assert!(
601 cache.get(&1).is_none(),
602 "key=1 should be evicted (capacity=1)"
603 );
604 assert!(cache.get(&2).is_some());
605 }
606
607 #[test]
608 fn test_empty_cache_get() {
609 let mut cache: L1Cache<i32> = L1Cache::new(10);
610 assert!(cache.get(&1).is_none());
611 assert_eq!(cache.stats().misses, 1);
612 }
613
614 #[test]
615 fn test_default_capacity() {
616 let cache: L1Cache<i32> = L1Cache::default();
617 assert_eq!(cache.capacity(), 1024);
618 }
619}