1use super::cache_trait::Cache;
4use super::entry::CacheEntry;
5use super::layered::LayeredCacheStore;
6use super::statistics::{CacheEntryInfo, CacheStatistics};
7use async_trait::async_trait;
8use reinhardt_core::exception::{Error, Result};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicU64, Ordering};
13use std::time::{Duration, SystemTime};
14use tokio::sync::RwLock;
15use tokio::task::AbortHandle;
16
17#[derive(Clone, Copy, Debug)]
19pub enum CleanupStrategy {
20 Naive,
25 Layered,
34}
35
36#[derive(Clone)]
38pub struct InMemoryCache {
39 store: Arc<RwLock<HashMap<String, CacheEntry>>>,
40 layered_store: Option<LayeredCacheStore>,
41 cleanup_strategy: CleanupStrategy,
42 default_ttl: Option<Duration>,
43 hits: Arc<AtomicU64>,
44 misses: Arc<AtomicU64>,
45 cleanup_interval: Option<Duration>,
46 cleanup_handle: Arc<std::sync::Mutex<Option<AbortHandle>>>,
48}
49
50impl InMemoryCache {
51 pub fn new() -> Self {
65 Self {
66 store: Arc::new(RwLock::new(HashMap::new())),
67 layered_store: None,
68 cleanup_strategy: CleanupStrategy::Naive,
69 default_ttl: None,
70 hits: Arc::new(AtomicU64::new(0)),
71 misses: Arc::new(AtomicU64::new(0)),
72 cleanup_interval: None,
73 cleanup_handle: Arc::new(std::sync::Mutex::new(None)),
74 }
75 }
76
77 pub fn with_layered_cleanup() -> Self {
99 Self {
100 store: Arc::new(RwLock::new(HashMap::new())),
101 layered_store: Some(LayeredCacheStore::new()),
102 cleanup_strategy: CleanupStrategy::Layered,
103 default_ttl: None,
104 hits: Arc::new(AtomicU64::new(0)),
105 misses: Arc::new(AtomicU64::new(0)),
106 cleanup_interval: None,
107 cleanup_handle: Arc::new(std::sync::Mutex::new(None)),
108 }
109 }
110
111 pub fn with_custom_layered_cleanup(sample_size: usize, threshold: f32) -> Self {
127 Self {
128 store: Arc::new(RwLock::new(HashMap::new())),
129 layered_store: Some(LayeredCacheStore::with_sampler(sample_size, threshold)),
130 cleanup_strategy: CleanupStrategy::Layered,
131 default_ttl: None,
132 hits: Arc::new(AtomicU64::new(0)),
133 misses: Arc::new(AtomicU64::new(0)),
134 cleanup_interval: None,
135 cleanup_handle: Arc::new(std::sync::Mutex::new(None)),
136 }
137 }
138 pub fn with_default_ttl(mut self, ttl: Duration) -> Self {
161 self.default_ttl = Some(ttl);
162 self
163 }
164 pub async fn cleanup_expired(&self) {
198 match self.cleanup_strategy {
199 CleanupStrategy::Naive => {
200 let mut store = self.store.write().await;
201 store.retain(|_, entry| !entry.is_expired());
202 }
203 CleanupStrategy::Layered => {
204 if let Some(ref layered_store) = self.layered_store {
205 layered_store.cleanup().await;
206 }
207 }
208 }
209 }
210
211 pub async fn get_statistics(&self) -> CacheStatistics {
238 let hits = self.hits.load(Ordering::Relaxed);
239 let misses = self.misses.load(Ordering::Relaxed);
240
241 let (entry_count, memory_usage) = match self.cleanup_strategy {
242 CleanupStrategy::Naive => {
243 let store = self.store.read().await;
244 let entry_count = store.len() as u64;
245 let memory_usage = store
246 .values()
247 .map(|entry| entry.value.len() as u64)
248 .sum::<u64>();
249 (entry_count, memory_usage)
250 }
251 CleanupStrategy::Layered => {
252 if let Some(ref layered_store) = self.layered_store {
253 let store_clone = layered_store.get_store_clone().await;
254 let entry_count = store_clone.len() as u64;
255 let memory_usage = store_clone
256 .values()
257 .map(|entry| entry.value.len() as u64)
258 .sum::<u64>();
259 (entry_count, memory_usage)
260 } else {
261 (0, 0)
262 }
263 }
264 };
265
266 CacheStatistics {
267 hits,
268 misses,
269 total_requests: hits + misses,
270 entry_count,
271 memory_usage,
272 }
273 }
274
275 pub async fn list_keys(&self) -> Vec<String> {
300 match self.cleanup_strategy {
301 CleanupStrategy::Naive => {
302 let store = self.store.read().await;
303 store.keys().cloned().collect()
304 }
305 CleanupStrategy::Layered => {
306 if let Some(ref layered_store) = self.layered_store {
307 layered_store.keys().await
308 } else {
309 Vec::new()
310 }
311 }
312 }
313 }
314
315 pub async fn inspect_entry_with_timestamps(
347 &self,
348 key: &str,
349 ) -> Result<Option<(SystemTime, Option<SystemTime>)>> {
350 match self.cleanup_strategy {
351 CleanupStrategy::Naive => {
352 let store = self.store.read().await;
353
354 if let Some(entry) = store.get(key) {
355 if entry.is_expired() {
356 return Ok(None);
357 }
358
359 Ok(Some((entry.created_at, entry.accessed_at)))
360 } else {
361 Ok(None)
362 }
363 }
364 CleanupStrategy::Layered => {
365 if let Some(ref layered_store) = self.layered_store {
366 Ok(layered_store.get_entry_timestamps(key).await)
367 } else {
368 Ok(None)
369 }
370 }
371 }
372 }
373
374 pub async fn inspect_entry(&self, key: &str) -> Option<CacheEntryInfo> {
407 let entry = match self.cleanup_strategy {
408 CleanupStrategy::Naive => {
409 let store = self.store.read().await;
410 store.get(key).cloned()
411 }
412 CleanupStrategy::Layered => {
413 if let Some(ref layered_store) = self.layered_store {
414 layered_store.get_entry(key).await
415 } else {
416 None
417 }
418 }
419 };
420
421 entry.map(|entry| {
422 let ttl_seconds = entry.expires_at.and_then(|expires_at| {
423 expires_at
424 .duration_since(SystemTime::now())
425 .ok()
426 .map(|d| d.as_secs())
427 });
428
429 CacheEntryInfo {
430 key: key.to_string(),
431 size: entry.value.len(),
432 has_expiry: entry.expires_at.is_some(),
433 ttl_seconds,
434 }
435 })
436 }
437
438 pub fn start_auto_cleanup(&self, interval: Duration) {
459 let mut handle_guard = self
460 .cleanup_handle
461 .lock()
462 .unwrap_or_else(|e| e.into_inner());
463
464 if let Some(existing) = handle_guard.take() {
466 existing.abort();
467 }
468
469 let cache = self.clone();
470 let abort_handle = tokio::spawn(async move {
471 let mut interval_timer = tokio::time::interval(interval);
472 loop {
473 interval_timer.tick().await;
474 cache.cleanup_expired().await;
475 }
476 })
477 .abort_handle();
478
479 *handle_guard = Some(abort_handle);
480 }
481
482 pub fn stop_auto_cleanup(&self) {
487 let mut handle_guard = self
488 .cleanup_handle
489 .lock()
490 .unwrap_or_else(|e| e.into_inner());
491 if let Some(handle) = handle_guard.take() {
492 handle.abort();
493 }
494 }
495
496 pub fn with_auto_cleanup(mut self, interval: Duration) -> Self {
515 self.cleanup_interval = Some(interval);
516 self.start_auto_cleanup(interval);
517 self
518 }
519}
520
521impl Default for InMemoryCache {
522 fn default() -> Self {
523 Self::new()
524 }
525}
526
527#[async_trait]
528impl Cache for InMemoryCache {
529 async fn get<T>(&self, key: &str) -> Result<Option<T>>
530 where
531 T: for<'de> Deserialize<'de> + Send,
532 {
533 match self.cleanup_strategy {
534 CleanupStrategy::Naive => {
535 let mut store = self.store.write().await;
537
538 if let Some(entry) = store.get_mut(key) {
539 if entry.is_expired() {
540 self.misses.fetch_add(1, Ordering::Relaxed);
542 return Ok(None);
543 }
544
545 entry.touch();
547 self.hits.fetch_add(1, Ordering::Relaxed);
548
549 let value = serde_json::from_slice(&entry.value)
550 .map_err(|e| Error::Serialization(e.to_string()))?;
551 Ok(Some(value))
552 } else {
553 self.misses.fetch_add(1, Ordering::Relaxed);
555 Ok(None)
556 }
557 }
558 CleanupStrategy::Layered => {
559 if let Some(ref layered_store) = self.layered_store {
560 if let Some(data) = layered_store.get(key).await {
561 self.hits.fetch_add(1, Ordering::Relaxed);
563 let value = serde_json::from_slice(&data)
564 .map_err(|e| Error::Serialization(e.to_string()))?;
565 Ok(Some(value))
566 } else {
567 self.misses.fetch_add(1, Ordering::Relaxed);
569 Ok(None)
570 }
571 } else {
572 self.misses.fetch_add(1, Ordering::Relaxed);
573 Ok(None)
574 }
575 }
576 }
577 }
578
579 async fn set<T>(&self, key: &str, value: &T, ttl: Option<Duration>) -> Result<()>
580 where
581 T: Serialize + Send + Sync,
582 {
583 let serialized =
584 serde_json::to_vec(value).map_err(|e| Error::Serialization(e.to_string()))?;
585
586 let ttl = ttl.or(self.default_ttl);
587
588 match self.cleanup_strategy {
589 CleanupStrategy::Naive => {
590 let entry = CacheEntry::new(serialized, ttl);
591 let mut store = self.store.write().await;
592 store.insert(key.to_string(), entry);
593 }
594 CleanupStrategy::Layered => {
595 if let Some(ref layered_store) = self.layered_store {
596 layered_store.set(key.to_string(), serialized, ttl).await;
597 }
598 }
599 }
600
601 Ok(())
602 }
603
604 async fn delete(&self, key: &str) -> Result<()> {
605 match self.cleanup_strategy {
606 CleanupStrategy::Naive => {
607 let mut store = self.store.write().await;
608 store.remove(key);
609 }
610 CleanupStrategy::Layered => {
611 if let Some(ref layered_store) = self.layered_store {
612 layered_store.delete(key).await;
613 }
614 }
615 }
616 Ok(())
617 }
618
619 async fn has_key(&self, key: &str) -> Result<bool> {
620 match self.cleanup_strategy {
621 CleanupStrategy::Naive => {
622 let store = self.store.read().await;
623
624 if let Some(entry) = store.get(key) {
625 Ok(!entry.is_expired())
626 } else {
627 Ok(false)
628 }
629 }
630 CleanupStrategy::Layered => {
631 if let Some(ref layered_store) = self.layered_store {
632 Ok(layered_store.has_key(key).await)
633 } else {
634 Ok(false)
635 }
636 }
637 }
638 }
639
640 async fn clear(&self) -> Result<()> {
641 match self.cleanup_strategy {
642 CleanupStrategy::Naive => {
643 let mut store = self.store.write().await;
644 store.clear();
645 }
646 CleanupStrategy::Layered => {
647 if let Some(ref layered_store) = self.layered_store {
648 layered_store.clear().await;
649 }
650 }
651 }
652 Ok(())
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659
660 async fn poll_until<F, Fut>(
662 timeout: std::time::Duration,
663 interval: std::time::Duration,
664 mut condition: F,
665 ) -> std::result::Result<(), String>
666 where
667 F: FnMut() -> Fut,
668 Fut: std::future::Future<Output = bool>,
669 {
670 let start = std::time::Instant::now();
671 while start.elapsed() < timeout {
672 if condition().await {
673 return Ok(());
674 }
675 tokio::time::sleep(interval).await;
676 }
677 Err(format!("Timeout after {:?} waiting for condition", timeout))
678 }
679
680 #[tokio::test]
681 async fn test_in_memory_cache_basic() {
682 let cache = InMemoryCache::new();
683
684 cache.set("key1", &"value1", None).await.unwrap();
686 let value: Option<String> = cache.get("key1").await.unwrap();
687 assert_eq!(value, Some("value1".to_string()));
688
689 assert!(cache.has_key("key1").await.unwrap());
691 assert!(!cache.has_key("key2").await.unwrap());
692
693 cache.delete("key1").await.unwrap();
695 let value: Option<String> = cache.get("key1").await.unwrap();
696 assert_eq!(value, None);
697 }
698
699 #[tokio::test]
700 async fn test_in_memory_cache_ttl() {
701 let cache = InMemoryCache::new();
702
703 cache
705 .set("key1", &"value1", Some(Duration::from_millis(100)))
706 .await
707 .unwrap();
708
709 let value: Option<String> = cache.get("key1").await.unwrap();
711 assert_eq!(value, Some("value1".to_string()));
712
713 poll_until(
715 Duration::from_millis(200),
716 Duration::from_millis(10),
717 || async {
718 let value: Option<String> = cache.get("key1").await.unwrap();
719 value.is_none()
720 },
721 )
722 .await
723 .expect("Key should expire within 200ms");
724 }
725
726 #[tokio::test]
727 async fn test_in_memory_cache_many() {
728 let cache = InMemoryCache::new();
729
730 let mut values = HashMap::new();
732 values.insert("key1".to_string(), "value1".to_string());
733 values.insert("key2".to_string(), "value2".to_string());
734 cache.set_many(values, None).await.unwrap();
735
736 let results: HashMap<String, String> =
738 cache.get_many(&["key1", "key2", "key3"]).await.unwrap();
739 assert_eq!(results.len(), 2);
740 assert_eq!(results.get("key1"), Some(&"value1".to_string()));
741 assert_eq!(results.get("key2"), Some(&"value2".to_string()));
742
743 cache.delete_many(&["key1", "key2"]).await.unwrap();
745 assert!(!cache.has_key("key1").await.unwrap());
746 assert!(!cache.has_key("key2").await.unwrap());
747 }
748
749 #[tokio::test]
750 async fn test_in_memory_cache_incr_decr() {
751 let cache = InMemoryCache::new();
752
753 let value = cache.incr("counter", 5).await.unwrap();
755 assert_eq!(value, 5);
756
757 let value = cache.incr("counter", 3).await.unwrap();
759 assert_eq!(value, 8);
760
761 let value = cache.decr("counter", 2).await.unwrap();
763 assert_eq!(value, 6);
764 }
765
766 #[tokio::test]
767 async fn test_in_memory_cache_clear() {
768 let cache = InMemoryCache::new();
769
770 cache.set("key1", &"value1", None).await.unwrap();
771 cache.set("key2", &"value2", None).await.unwrap();
772
773 cache.clear().await.unwrap();
774
775 assert!(!cache.has_key("key1").await.unwrap());
776 assert!(!cache.has_key("key2").await.unwrap());
777 }
778
779 #[tokio::test]
780 async fn test_cache_cleanup_expired() {
781 let cache = InMemoryCache::new();
782
783 cache
785 .set("key1", &"value1", Some(Duration::from_millis(100)))
786 .await
787 .unwrap();
788 cache.set("key2", &"value2", None).await.unwrap();
789
790 poll_until(
792 Duration::from_millis(200),
793 Duration::from_millis(10),
794 || async {
795 let value: Option<String> = cache.get("key1").await.unwrap();
796 value.is_none()
797 },
798 )
799 .await
800 .expect("Key1 should expire within 200ms");
801
802 cache.cleanup_expired().await;
804
805 assert!(!cache.has_key("key1").await.unwrap());
807 assert!(cache.has_key("key2").await.unwrap());
808 }
809
810 #[tokio::test]
811 async fn test_cache_statistics_basic() {
812 let cache = InMemoryCache::new();
813
814 let stats = cache.get_statistics().await;
816 assert_eq!(stats.hits, 0);
817 assert_eq!(stats.misses, 0);
818 assert_eq!(stats.total_requests, 0);
819 assert_eq!(stats.entry_count, 0);
820 assert_eq!(stats.memory_usage, 0);
821
822 cache.set("key1", &"value1", None).await.unwrap();
824 cache.set("key2", &"value2", None).await.unwrap();
825
826 let stats = cache.get_statistics().await;
828 assert_eq!(stats.entry_count, 2);
829 assert!(stats.memory_usage > 0);
830
831 let _: Option<String> = cache.get("key1").await.unwrap();
833 let _: Option<String> = cache.get("key2").await.unwrap();
834
835 let stats = cache.get_statistics().await;
836 assert_eq!(stats.hits, 2);
837 assert_eq!(stats.misses, 0);
838 assert_eq!(stats.total_requests, 2);
839
840 let _: Option<String> = cache.get("key3").await.unwrap();
842
843 let stats = cache.get_statistics().await;
844 assert_eq!(stats.hits, 2);
845 assert_eq!(stats.misses, 1);
846 assert_eq!(stats.total_requests, 3);
847 }
848
849 #[tokio::test]
850 async fn test_cache_statistics_hit_miss_rate() {
851 let cache = InMemoryCache::new();
852
853 cache.set("key1", &"value1", None).await.unwrap();
854 cache.set("key2", &"value2", None).await.unwrap();
855
856 let _: Option<String> = cache.get("key1").await.unwrap();
858 let _: Option<String> = cache.get("key2").await.unwrap();
859
860 let _: Option<String> = cache.get("key3").await.unwrap();
862
863 let stats = cache.get_statistics().await;
864 assert_eq!(stats.hit_rate(), 2.0 / 3.0);
865 assert_eq!(stats.miss_rate(), 1.0 / 3.0);
866 }
867
868 #[tokio::test]
869 async fn test_cache_statistics_expired_counts_as_miss() {
870 let cache = InMemoryCache::new();
871
872 cache
874 .set("key1", &"value1", Some(Duration::from_millis(10)))
875 .await
876 .unwrap();
877
878 tokio::time::sleep(Duration::from_millis(15)).await;
880
881 let value: Option<String> = cache.get("key1").await.unwrap();
883 assert!(value.is_none(), "Key should have expired");
884
885 let stats = cache.get_statistics().await;
887 assert_eq!(stats.hits, 0, "Expected 0 hits, got {}", stats.hits);
888 assert_eq!(stats.misses, 1, "Expected 1 miss, got {}", stats.misses);
889 }
890
891 #[tokio::test]
892 async fn test_cache_statistics_memory_usage() {
893 let cache = InMemoryCache::new();
894
895 cache.set("key1", &"short", None).await.unwrap();
897 cache.set("key2", &"a longer value", None).await.unwrap();
898
899 let stats = cache.get_statistics().await;
900 assert!(stats.memory_usage > 0);
901
902 let initial_usage = stats.memory_usage;
904
905 cache
906 .set("key3", &"even longer value here", None)
907 .await
908 .unwrap();
909
910 let stats = cache.get_statistics().await;
911 assert!(stats.memory_usage > initial_usage);
912 }
913
914 #[tokio::test]
915 async fn test_list_keys() {
916 let cache = InMemoryCache::new();
917
918 let keys = cache.list_keys().await;
920 assert_eq!(keys.len(), 0);
921
922 cache.set("key1", &"value1", None).await.unwrap();
924 cache.set("key2", &"value2", None).await.unwrap();
925 cache.set("key3", &"value3", None).await.unwrap();
926
927 let keys = cache.list_keys().await;
928 assert_eq!(keys.len(), 3);
929 assert!(keys.contains(&"key1".to_string()));
930 assert!(keys.contains(&"key2".to_string()));
931 assert!(keys.contains(&"key3".to_string()));
932
933 cache.delete("key2").await.unwrap();
935
936 let keys = cache.list_keys().await;
937 assert_eq!(keys.len(), 2);
938 assert!(keys.contains(&"key1".to_string()));
939 assert!(!keys.contains(&"key2".to_string()));
940 assert!(keys.contains(&"key3".to_string()));
941 }
942
943 #[tokio::test]
944 async fn test_list_keys_includes_expired() {
945 let cache = InMemoryCache::new();
946
947 cache
949 .set("expired_key", &"value", Some(Duration::from_millis(10)))
950 .await
951 .unwrap();
952
953 cache.set("valid_key", &"value", None).await.unwrap();
955
956 poll_until(
958 Duration::from_millis(50),
959 Duration::from_millis(5),
960 || async {
961 let value: Option<String> = cache.get("expired_key").await.unwrap();
962 value.is_none()
963 },
964 )
965 .await
966 .expect("Expired key should expire within 50ms");
967
968 let keys = cache.list_keys().await;
970 assert_eq!(keys.len(), 2);
971 assert!(keys.contains(&"expired_key".to_string()));
972 assert!(keys.contains(&"valid_key".to_string()));
973
974 cache.cleanup_expired().await;
976 let keys = cache.list_keys().await;
977 assert_eq!(keys.len(), 1);
978 assert!(!keys.contains(&"expired_key".to_string()));
979 assert!(keys.contains(&"valid_key".to_string()));
980 }
981
982 #[tokio::test]
983 async fn test_inspect_entry_basic() {
984 let cache = InMemoryCache::new();
985
986 let info = cache.inspect_entry("nonexistent").await;
988 assert!(info.is_none());
989
990 cache.set("key1", &"value1", None).await.unwrap();
992
993 let info = cache.inspect_entry("key1").await;
994 let info = info.unwrap();
995 assert_eq!(info.key, "key1");
996 assert!(!info.has_expiry);
997 assert!(info.ttl_seconds.is_none());
998 assert!(info.size > 0);
999 }
1000
1001 #[tokio::test]
1002 async fn test_inspect_entry_with_ttl() {
1003 let cache = InMemoryCache::new();
1004
1005 cache
1007 .set("key1", &"value1", Some(Duration::from_secs(300)))
1008 .await
1009 .unwrap();
1010
1011 let info = cache.inspect_entry("key1").await;
1012 let info = info.unwrap();
1013 assert_eq!(info.key, "key1");
1014 assert!(info.has_expiry);
1015 assert!(info.ttl_seconds.is_some());
1016
1017 let ttl = info.ttl_seconds.unwrap();
1019 assert!(ttl <= 300);
1020 assert!(ttl > 0);
1021 }
1022
1023 #[tokio::test]
1024 async fn test_inspect_entry_size() {
1025 let cache = InMemoryCache::new();
1026
1027 cache.set("small", &"x", None).await.unwrap();
1029 cache.set("large", &"x".repeat(1000), None).await.unwrap();
1030
1031 let small_info = cache.inspect_entry("small").await.unwrap();
1032 let large_info = cache.inspect_entry("large").await.unwrap();
1033
1034 assert!(large_info.size > small_info.size);
1035 }
1036
1037 #[tokio::test]
1038 async fn test_inspect_entry_expired() {
1039 let cache = InMemoryCache::new();
1040
1041 cache
1043 .set("key1", &"value1", Some(Duration::from_millis(10)))
1044 .await
1045 .unwrap();
1046
1047 let info = cache.inspect_entry("key1").await;
1049 assert!(info.is_some());
1050
1051 poll_until(
1053 Duration::from_millis(50),
1054 Duration::from_millis(5),
1055 || async {
1056 let value: Option<String> = cache.get("key1").await.unwrap();
1057 value.is_none()
1058 },
1059 )
1060 .await
1061 .expect("Key should expire within 50ms");
1062
1063 let info = cache.inspect_entry("key1").await;
1065 let info = info.unwrap();
1066 assert!(info.has_expiry);
1067 assert!(info.ttl_seconds.is_none());
1069
1070 cache.cleanup_expired().await;
1072 let info = cache.inspect_entry("key1").await;
1073 assert!(info.is_none());
1074 }
1075
1076 #[tokio::test]
1077 async fn test_start_auto_cleanup() {
1078 let cache = InMemoryCache::new();
1079
1080 cache
1082 .set("key1", &"value1", Some(Duration::from_millis(50)))
1083 .await
1084 .unwrap();
1085 cache
1086 .set("key2", &"value2", Some(Duration::from_millis(50)))
1087 .await
1088 .unwrap();
1089
1090 cache.start_auto_cleanup(Duration::from_millis(30));
1092
1093 assert!(cache.has_key("key1").await.unwrap());
1095 assert!(cache.has_key("key2").await.unwrap());
1096
1097 poll_until(
1099 Duration::from_millis(200),
1100 Duration::from_millis(10),
1101 || async {
1102 !cache.has_key("key1").await.unwrap() && !cache.has_key("key2").await.unwrap()
1103 },
1104 )
1105 .await
1106 .expect("Keys should be auto-cleaned within 200ms");
1107
1108 assert!(!cache.has_key("key1").await.unwrap());
1110 assert!(!cache.has_key("key2").await.unwrap());
1111 }
1112
1113 #[tokio::test]
1114 async fn test_with_auto_cleanup() {
1115 let cache = InMemoryCache::new().with_auto_cleanup(Duration::from_millis(30));
1116
1117 cache
1119 .set("key1", &"value1", Some(Duration::from_millis(50)))
1120 .await
1121 .unwrap();
1122 cache
1123 .set("key2", &"value2", Some(Duration::from_millis(50)))
1124 .await
1125 .unwrap();
1126
1127 assert!(cache.has_key("key1").await.unwrap());
1129 assert!(cache.has_key("key2").await.unwrap());
1130
1131 poll_until(
1133 Duration::from_millis(200),
1134 Duration::from_millis(10),
1135 || async {
1136 !cache.has_key("key1").await.unwrap() && !cache.has_key("key2").await.unwrap()
1137 },
1138 )
1139 .await
1140 .expect("Keys should be auto-cleaned within 200ms");
1141 }
1142
1143 #[tokio::test]
1144 async fn test_stop_auto_cleanup() {
1145 let cache = InMemoryCache::new();
1146
1147 cache.start_auto_cleanup(Duration::from_millis(30));
1149
1150 cache
1152 .set("key1", &"value1", Some(Duration::from_millis(50)))
1153 .await
1154 .unwrap();
1155
1156 cache.stop_auto_cleanup();
1158
1159 tokio::time::sleep(Duration::from_millis(150)).await;
1161
1162 let value: Option<String> = cache.get("key1").await.unwrap();
1164 assert!(value.is_none(), "Key should be expired");
1165 }
1166
1167 #[tokio::test]
1168 async fn test_start_auto_cleanup_replaces_previous() {
1169 let cache = InMemoryCache::new();
1170
1171 cache.start_auto_cleanup(Duration::from_millis(30));
1173 cache.start_auto_cleanup(Duration::from_millis(30));
1174
1175 cache
1177 .set("key1", &"value1", Some(Duration::from_millis(50)))
1178 .await
1179 .unwrap();
1180
1181 poll_until(
1183 Duration::from_millis(200),
1184 Duration::from_millis(10),
1185 || async { !cache.has_key("key1").await.unwrap() },
1186 )
1187 .await
1188 .expect("Key should be cleaned up");
1189 }
1190
1191 #[tokio::test]
1192 async fn test_auto_cleanup_preserves_non_expired() {
1193 let cache = InMemoryCache::new();
1194
1195 cache.start_auto_cleanup(Duration::from_millis(30));
1197
1198 cache
1200 .set("short_lived", &"value1", Some(Duration::from_millis(50)))
1201 .await
1202 .unwrap();
1203 cache.set("long_lived", &"value2", None).await.unwrap();
1204
1205 assert!(cache.has_key("short_lived").await.unwrap());
1207 assert!(cache.has_key("long_lived").await.unwrap());
1208
1209 poll_until(
1211 Duration::from_millis(200),
1212 Duration::from_millis(10),
1213 || async {
1214 !cache.has_key("short_lived").await.unwrap()
1215 && cache.has_key("long_lived").await.unwrap()
1216 },
1217 )
1218 .await
1219 .expect("Short-lived key should be cleaned, long-lived should remain");
1220 }
1221
1222 #[tokio::test]
1225 async fn test_layered_cache_basic() {
1226 let cache = InMemoryCache::with_layered_cleanup();
1227
1228 cache.set("key1", &"value1", None).await.unwrap();
1230 let value: Option<String> = cache.get("key1").await.unwrap();
1231 assert_eq!(value, Some("value1".to_string()));
1232
1233 assert!(cache.has_key("key1").await.unwrap());
1235 assert!(!cache.has_key("key2").await.unwrap());
1236
1237 cache.delete("key1").await.unwrap();
1239 let value: Option<String> = cache.get("key1").await.unwrap();
1240 assert_eq!(value, None);
1241 }
1242
1243 #[tokio::test]
1244 async fn test_layered_cache_ttl() {
1245 let cache = InMemoryCache::with_layered_cleanup();
1246
1247 cache
1249 .set("key1", &"value1", Some(Duration::from_millis(100)))
1250 .await
1251 .unwrap();
1252
1253 let value: Option<String> = cache.get("key1").await.unwrap();
1255 assert_eq!(value, Some("value1".to_string()));
1256
1257 poll_until(
1259 Duration::from_millis(200),
1260 Duration::from_millis(10),
1261 || async {
1262 let value: Option<String> = cache.get("key1").await.unwrap();
1263 value.is_none()
1264 },
1265 )
1266 .await
1267 .expect("Key should expire within 200ms");
1268 }
1269
1270 #[tokio::test]
1271 async fn test_layered_cleanup_expired() {
1272 let cache = InMemoryCache::with_layered_cleanup();
1273
1274 cache
1276 .set("key1", &"value1", Some(Duration::from_millis(50)))
1277 .await
1278 .unwrap();
1279 cache.set("key2", &"value2", None).await.unwrap();
1280
1281 tokio::time::sleep(Duration::from_millis(100)).await;
1283
1284 cache.cleanup_expired().await;
1286
1287 assert!(!cache.has_key("key1").await.unwrap());
1289 assert!(cache.has_key("key2").await.unwrap());
1290 }
1291
1292 #[tokio::test]
1293 async fn test_layered_cache_statistics() {
1294 let cache = InMemoryCache::with_layered_cleanup();
1295
1296 cache.set("key1", &"value1", None).await.unwrap();
1298 cache.set("key2", &"value2", None).await.unwrap();
1299
1300 let stats = cache.get_statistics().await;
1301 assert_eq!(stats.entry_count, 2);
1302
1303 let _: Option<String> = cache.get("key1").await.unwrap();
1305 let _: Option<String> = cache.get("key2").await.unwrap();
1306
1307 let stats = cache.get_statistics().await;
1308 assert_eq!(stats.hits, 2);
1309 assert_eq!(stats.misses, 0);
1310
1311 let _: Option<String> = cache.get("key3").await.unwrap();
1313
1314 let stats = cache.get_statistics().await;
1315 assert_eq!(stats.hits, 2);
1316 assert_eq!(stats.misses, 1);
1317 }
1318
1319 #[tokio::test]
1320 async fn test_layered_list_keys() {
1321 let cache = InMemoryCache::with_layered_cleanup();
1322
1323 let keys = cache.list_keys().await;
1325 assert_eq!(keys.len(), 0);
1326
1327 cache.set("key1", &"value1", None).await.unwrap();
1329 cache.set("key2", &"value2", None).await.unwrap();
1330 cache.set("key3", &"value3", None).await.unwrap();
1331
1332 let keys = cache.list_keys().await;
1333 assert_eq!(keys.len(), 3);
1334 assert!(keys.contains(&"key1".to_string()));
1335 assert!(keys.contains(&"key2".to_string()));
1336 assert!(keys.contains(&"key3".to_string()));
1337 }
1338
1339 #[tokio::test]
1340 async fn test_layered_inspect_entry() {
1341 let cache = InMemoryCache::with_layered_cleanup();
1342
1343 let info = cache.inspect_entry("nonexistent").await;
1345 assert!(info.is_none());
1346
1347 cache
1349 .set("key1", &"value1", Some(Duration::from_secs(300)))
1350 .await
1351 .unwrap();
1352
1353 let info = cache.inspect_entry("key1").await;
1354 let info = info.unwrap();
1355 assert_eq!(info.key, "key1");
1356 assert!(info.has_expiry);
1357 assert!(info.ttl_seconds.is_some());
1358 assert!(info.ttl_seconds.unwrap() <= 300);
1359 }
1360
1361 #[tokio::test]
1362 async fn test_layered_large_dataset() {
1363 let cache = InMemoryCache::with_layered_cleanup();
1364
1365 let num_keys = 1000;
1367 for i in 0..num_keys {
1368 cache
1369 .set(
1370 &format!("key{}", i),
1371 &format!("value{}", i),
1372 Some(Duration::from_millis(50)),
1373 )
1374 .await
1375 .unwrap();
1376 }
1377
1378 let stats = cache.get_statistics().await;
1380 assert_eq!(stats.entry_count, num_keys);
1381
1382 tokio::time::sleep(Duration::from_millis(60)).await;
1384
1385 cache.cleanup_expired().await;
1387
1388 let stats = cache.get_statistics().await;
1390 assert_eq!(stats.entry_count, 0);
1391 }
1392
1393 #[tokio::test]
1394 async fn test_custom_layered_cleanup() {
1395 let cache = InMemoryCache::with_custom_layered_cleanup(50, 0.30);
1397
1398 for i in 0..100 {
1400 cache
1401 .set(
1402 &format!("key{}", i),
1403 &format!("value{}", i),
1404 Some(Duration::from_millis(50)),
1405 )
1406 .await
1407 .unwrap();
1408 }
1409
1410 tokio::time::sleep(Duration::from_millis(100)).await;
1412
1413 cache.cleanup_expired().await;
1415
1416 let stats = cache.get_statistics().await;
1418 assert_eq!(stats.entry_count, 0);
1419 }
1420}