1use std::collections::HashMap;
37
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>, u64)>,
90 clock: u64,
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 clock: 0,
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 self.clock += 1;
128
129 if let Some(entry) = self.data.get_mut(&key) {
130 entry.0 = value;
131 entry.1 = self.clock;
132 return;
133 }
134
135 if self.data.len() >= self.capacity {
136 let victim = self
137 .data
138 .iter()
139 .min_by_key(|(_, (_, ts))| *ts)
140 .map(|(k, _)| *k);
141 if let Some(victim) = victim {
142 self.data.remove(&victim);
143 self.evicts.fetch_add(1, Ordering::Relaxed);
144 }
145 }
146
147 self.data.insert(key, (value, self.clock));
148 }
149
150 pub fn get(&mut self, key: &i64) -> Option<Arc<T>> {
154 if let Some(entry) = self.data.get_mut(key) {
155 self.clock += 1;
156 entry.1 = self.clock;
157 self.hits.fetch_add(1, Ordering::Relaxed);
158 Some(Arc::clone(&entry.0))
159 } else {
160 self.misses.fetch_add(1, Ordering::Relaxed);
161 None
162 }
163 }
164
165 pub fn evict(&mut self, key: &i64) {
167 self.data.remove(key);
168 }
169
170 pub fn clear(&mut self) {
172 self.data.clear();
173 }
174
175 pub fn len(&self) -> usize {
177 self.data.len()
178 }
179
180 pub fn is_empty(&self) -> bool {
182 self.data.is_empty()
183 }
184
185 pub fn stats(&self) -> L1CacheStats {
187 L1CacheStats {
188 hits: self.hits.load(Ordering::Relaxed),
189 misses: self.misses.load(Ordering::Relaxed),
190 entry_count: self.data.len(),
191 evict_count: self.evicts.load(Ordering::Relaxed),
192 }
193 }
194}
195
196impl<T> Default for L1Cache<T> {
197 fn default() -> Self {
198 Self::new(1024)
199 }
200}
201
202pub struct L1L2Coordinator<T: Clone> {
213 l1: L1Cache<T>,
215 l2: Option<std::sync::Arc<crate::l2_cache::L2Cache>>,
217}
218
219impl<T: Clone> L1L2Coordinator<T> {
220 pub fn new(l1_capacity: usize) -> Self {
222 Self {
223 l1: L1Cache::new(l1_capacity),
224 l2: None,
225 }
226 }
227
228 pub fn with_l2(mut self, l2: std::sync::Arc<crate::l2_cache::L2Cache>) -> Self {
230 self.l2 = Some(l2);
231 self
232 }
233
234 pub fn get_or_load<F>(&mut self, table: &str, pk: i64, db_loader: F) -> Option<Arc<T>>
242 where
243 F: FnOnce() -> Option<T>,
244 {
245 if let Some(val) = self.l1.get(&pk) {
247 return Some(val);
248 }
249
250 if let Some(l2) = &self.l2 {
252 let l2_key = crate::l2_cache::CacheKey::by_pk(table, pk);
253 if let Some(crate::value::Value::String(s)) = l2.get(&l2_key) {
254 let val = Arc::new(T::clone(&db_loader().unwrap()));
256 let _ = s;
257 self.l1.put(pk, val.clone());
258 return Some(val);
259 }
260 }
261
262 if let Some(val) = db_loader() {
264 let arc_val = Arc::new(val);
265 self.l1.put(pk, arc_val.clone());
266 if let Some(l2) = &self.l2 {
268 let l2_key = crate::l2_cache::CacheKey::by_pk(table, pk);
269 l2.put(
270 &l2_key,
271 crate::value::Value::String(format!("{}", pk)),
272 None,
273 );
274 }
275 return Some(arc_val);
276 }
277
278 None
279 }
280
281 pub fn invalidate(&mut self, pk: i64) {
283 self.l1.evict(&pk);
284 }
285
286 pub fn clear(&mut self) {
288 self.l1.clear();
289 }
290
291 pub fn l1_stats(&self) -> L1CacheStats {
293 self.l1.stats()
294 }
295
296 pub fn l1_mut(&mut self) -> &mut L1Cache<T> {
298 &mut self.l1
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
309 fn test_identity_map_same_ptr() {
310 let mut cache: L1Cache<String> = L1Cache::new(10);
311 cache.put(1, Arc::new("Alice".to_string()));
312
313 let a = cache.get(&1).unwrap();
314 let b = cache.get(&1).unwrap();
315 assert!(
316 Arc::ptr_eq(&a, &b),
317 "Identity Map: same key must return same Arc ptr"
318 );
319 }
320
321 #[test]
322 fn test_identity_map_different_keys_different_ptrs() {
323 let mut cache: L1Cache<String> = L1Cache::new(10);
324 cache.put(1, Arc::new("Alice".to_string()));
325 cache.put(2, Arc::new("Bob".to_string()));
326
327 let a = cache.get(&1).unwrap();
328 let b = cache.get(&2).unwrap();
329 assert!(
330 !Arc::ptr_eq(&a, &b),
331 "Different keys should return different Arc ptrs"
332 );
333 }
334
335 #[test]
338 fn test_lru_eviction() {
339 let mut cache: L1Cache<i32> = L1Cache::new(3);
340 cache.put(1, Arc::new(10));
341 cache.put(2, Arc::new(20));
342 cache.put(3, Arc::new(30));
343 assert_eq!(cache.len(), 3);
344
345 cache.put(4, Arc::new(40));
347 assert_eq!(cache.len(), 3);
348 assert!(cache.get(&1).is_none(), "key=1 should be evicted (LRU)");
349 assert!(cache.get(&4).is_some());
350
351 let stats = cache.stats();
352 assert!(stats.evict_count >= 1, "evict count should be >= 1");
353 }
354
355 #[test]
356 fn test_lru_touch_on_get() {
357 let mut cache: L1Cache<i32> = L1Cache::new(3);
358 cache.put(1, Arc::new(10));
359 cache.put(2, Arc::new(20));
360 cache.put(3, Arc::new(30));
361
362 let _ = cache.get(&1);
364
365 cache.put(4, Arc::new(40));
367 assert!(
368 cache.get(&1).is_some(),
369 "key=1 should still exist (was accessed)"
370 );
371 assert!(
372 cache.get(&2).is_none(),
373 "key=2 should be evicted (LRU after touch)"
374 );
375 }
376
377 #[test]
380 fn test_stats_hits_misses() {
381 let mut cache: L1Cache<String> = L1Cache::new(10);
382 cache.put(1, Arc::new("Alice".to_string()));
383
384 let _ = cache.get(&1); let _ = cache.get(&1); let _ = cache.get(&99); let stats = cache.stats();
389 assert_eq!(stats.hits, 2);
390 assert_eq!(stats.misses, 1);
391 assert_eq!(stats.entry_count, 1);
392 assert_eq!(stats.evict_count, 0);
393 }
394
395 #[test]
396 fn test_stats_hit_rate() {
397 let mut cache: L1Cache<i32> = L1Cache::new(10);
398 cache.put(1, Arc::new(100));
399
400 let _ = cache.get(&1); let _ = cache.get(&2); let _ = cache.get(&1); let stats = cache.stats();
405 assert_eq!(stats.total_lookups(), 3);
406 assert!((stats.hit_rate() - 2.0 / 3.0).abs() < 1e-9);
407 }
408
409 #[test]
412 fn test_session_drop_clears_cache() {
413 let stats;
414 {
415 let mut cache: L1Cache<String> = L1Cache::new(10);
416 cache.put(1, Arc::new("Alice".to_string()));
417 assert_eq!(cache.len(), 1);
418 stats = cache.stats();
419 }
421 assert_eq!(stats.entry_count, 1); }
423
424 #[test]
425 fn test_different_sessions_isolated() {
426 let mut cache_a: L1Cache<String> = L1Cache::new(10);
428 let mut cache_b: L1Cache<String> = L1Cache::new(10);
429
430 cache_a.put(1, Arc::new("from_session_a".to_string()));
431 cache_b.put(1, Arc::new("from_session_b".to_string()));
432
433 let a = cache_a.get(&1).unwrap();
434 let b = cache_b.get(&1).unwrap();
435 assert_eq!(*a, "from_session_a");
436 assert_eq!(*b, "from_session_b");
437 assert!(
438 !Arc::ptr_eq(&a, &b),
439 "Different sessions should have isolated caches"
440 );
441 }
442
443 #[test]
446 fn test_evict_single_key() {
447 let mut cache: L1Cache<String> = L1Cache::new(10);
448 cache.put(1, Arc::new("Alice".to_string()));
449 cache.put(2, Arc::new("Bob".to_string()));
450
451 cache.evict(&1);
452 assert!(cache.get(&1).is_none(), "key=1 should be evicted");
453 assert!(cache.get(&2).is_some(), "key=2 should still exist");
454 }
455
456 #[test]
457 fn test_clear_all() {
458 let mut cache: L1Cache<String> = L1Cache::new(10);
459 cache.put(1, Arc::new("Alice".to_string()));
460 cache.put(2, Arc::new("Bob".to_string()));
461
462 cache.clear();
463 assert!(cache.is_empty());
464 assert_eq!(cache.len(), 0);
465 }
466
467 #[test]
468 fn test_write_operation_evict() {
469 let mut cache: L1Cache<String> = L1Cache::new(10);
470 cache.put(1, Arc::new("Alice".to_string()));
471
472 cache.evict(&1);
474
475 let result = cache.get(&1);
477 assert!(result.is_none(), "After write evict, get should miss");
478
479 let stats = cache.stats();
480 assert_eq!(stats.misses, 1);
481 }
482
483 #[test]
486 fn test_object_consistency_after_update() {
487 let mut cache: L1Cache<String> = L1Cache::new(10);
488 cache.put(1, Arc::new("original".to_string()));
489
490 let a = cache.get(&1).unwrap();
491 assert_eq!(*a, "original");
492
493 cache.put(1, Arc::new("updated".to_string()));
495 let b = cache.get(&1).unwrap();
496 assert_eq!(*b, "updated");
497
498 assert_eq!(*a, "original");
500 assert_eq!(*b, "updated");
502 }
503
504 #[test]
507 fn test_atomic_stats_thread_safe() {
508 use std::sync::Arc;
509 use std::thread;
510
511 let cache = Arc::new(std::sync::Mutex::new(L1Cache::<i32>::new(100)));
512 let mut handles = Vec::new();
513
514 for i in 0..4 {
515 let cache_clone = Arc::clone(&cache);
516 handles.push(thread::spawn(move || {
517 let mut cache = cache_clone.lock().unwrap_or_else(|e| e.into_inner());
518 cache.put(i, Arc::new(i as i32));
519 let _ = cache.get(&i);
520 }));
521 }
522
523 for h in handles {
524 h.join().unwrap();
525 }
526
527 let cache = cache.lock().unwrap_or_else(|e| e.into_inner());
528 let stats = cache.stats();
529 assert_eq!(stats.entry_count, 4);
530 assert!(stats.hits >= 4);
531 }
532
533 #[test]
536 fn test_l1_l2_db_query_order() {
537 let mut coord: L1L2Coordinator<String> = L1L2Coordinator::new(10);
538
539 let db_call_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
541 let db_count_clone = Arc::clone(&db_call_count);
542
543 let result = coord.get_or_load("users", 1, || {
544 db_count_clone.fetch_add(1, Ordering::Relaxed);
545 Some("Alice".to_string())
546 });
547 assert_eq!(*result.unwrap(), "Alice");
548 assert_eq!(
549 db_call_count.load(Ordering::Relaxed),
550 1,
551 "DB should be called once"
552 );
553
554 let db_count_clone2 = Arc::clone(&db_call_count);
556 let result2 = coord.get_or_load("users", 1, || {
557 db_count_clone2.fetch_add(1, Ordering::Relaxed);
558 Some("Alice".to_string())
559 });
560 assert_eq!(*result2.unwrap(), "Alice");
561 assert_eq!(
562 db_call_count.load(Ordering::Relaxed),
563 1,
564 "DB should NOT be called again (L1 hit)"
565 );
566 }
567
568 #[test]
569 fn test_l1_l2_db_invalidate_after_write() {
570 let mut coord: L1L2Coordinator<String> = L1L2Coordinator::new(10);
571
572 let result = coord.get_or_load("users", 1, || Some("Alice".to_string()));
574 assert_eq!(*result.unwrap(), "Alice");
575
576 coord.invalidate(1);
578
579 let result2 = coord.get_or_load("users", 1, || Some("Bob".to_string()));
581 assert_eq!(
582 *result2.unwrap(),
583 "Bob",
584 "After invalidate, should reload from DB"
585 );
586 }
587
588 #[test]
591 fn test_capacity_one() {
592 let mut cache: L1Cache<i32> = L1Cache::new(1);
593 cache.put(1, Arc::new(10));
594 cache.put(2, Arc::new(20));
595
596 assert!(
597 cache.get(&1).is_none(),
598 "key=1 should be evicted (capacity=1)"
599 );
600 assert!(cache.get(&2).is_some());
601 }
602
603 #[test]
604 fn test_empty_cache_get() {
605 let mut cache: L1Cache<i32> = L1Cache::new(10);
606 assert!(cache.get(&1).is_none());
607 assert_eq!(cache.stats().misses, 1);
608 }
609
610 #[test]
611 fn test_default_capacity() {
612 let cache: L1Cache<i32> = L1Cache::default();
613 assert_eq!(cache.capacity(), 1024);
614 }
615}