1mod error;
9mod worker;
10mod object;
11mod policy;
12mod stats;
13
14use std::{
15 thread,
16 sync::{
17 Arc,
18 atomic::AtomicU64,
19 },
20 hash::{
21 Hash,
22 RandomState,
23 BuildHasher,
24 BuildHasherDefault,
25 },
26};
27
28use dashmap::{
29 DashMap,
30 mapref::entry::Entry,
31};
32
33use typesize::TypeSize;
34use nohash_hasher::NoHashHasher;
35use crossbeam_channel::unbounded;
36use log::info;
37
38use kwik::{
39 fmt,
40 math::set::Multiset,
41};
42
43use crate::{
44 stats::{AtomicStats, Stats},
45 object::{
46 Object,
47 ObjectSize,
48 overhead::OverheadManager,
49 },
50 worker::{
51 Worker,
52 WorkerSender,
53 WorkerEvent,
54 WorkerManager,
55 },
56};
57
58pub use crate::{
59 error::CacheError,
60 policy::PaperPolicy,
61};
62
63pub type CacheSize = u64;
64pub type AtomicCacheSize = AtomicU64;
65
66pub type HashedKey = u64;
67pub type NoHasher = BuildHasherDefault<NoHashHasher<HashedKey>>;
68
69pub type ObjectMapRef<K, V> = Arc<DashMap<HashedKey, Object<K, V>, NoHasher>>;
70pub type StatsRef = Arc<AtomicStats>;
71pub type OverheadManagerRef = Arc<OverheadManager>;
72
73pub struct PaperCache<K, V, S = RandomState> {
74 objects: ObjectMapRef<K, V>,
75 stats: StatsRef,
76
77 worker_manager: Arc<WorkerSender>,
78 overhead_manager: OverheadManagerRef,
79
80 hasher: S,
81}
82
83impl<K, V, S> PaperCache<K, V, S>
84where
85 K: 'static + Eq + Hash + TypeSize,
86 V: 'static + TypeSize,
87 S: Default + Clone + BuildHasher,
88{
89 pub fn new(
134 max_size: CacheSize,
135 policies: &[PaperPolicy],
136 policy: PaperPolicy,
137 ) -> Result<Self, CacheError> {
138 Self::with_hasher(
139 max_size,
140 policies,
141 policy,
142 Default::default(),
143 )
144 }
145
146 pub fn with_hasher(
164 max_size: CacheSize,
165 policies: &[PaperPolicy],
166 policy: PaperPolicy,
167 hasher: S,
168 ) -> Result<Self, CacheError> {
169 if max_size == 0 {
170 return Err(CacheError::ZeroCacheSize);
171 }
172
173 if policies.is_empty() {
174 return Err(CacheError::EmptyPolicies);
175 }
176
177 if policies.contains(&PaperPolicy::Auto) {
178 return Err(CacheError::ConfiguredAutoPolicy);
179 }
180
181 if policies.iter().is_multiset() {
182 return Err(CacheError::DuplicatePolicies);
183 }
184
185 if !policy.is_auto() && !policies.contains(&policy) {
186 return Err(CacheError::UnconfiguredPolicy);
187 }
188
189 let objects = Arc::new(DashMap::with_hasher(NoHasher::default()));
190 let stats = Arc::new(AtomicStats::new(max_size, policies, policy)?);
191 let overhead_manager = Arc::new(OverheadManager::new(&stats));
192
193 let (worker_sender, worker_listener) = unbounded();
194
195 let mut worker_manager = WorkerManager::new(
196 worker_listener,
197 &objects,
198 &stats,
199 &overhead_manager,
200 )?;
201
202 thread::spawn(move || worker_manager.run());
203
204 let cache = PaperCache {
205 objects,
206 stats,
207
208 worker_manager: Arc::new(worker_sender),
209 overhead_manager,
210
211 hasher,
212 };
213
214 Ok(cache)
215 }
216
217 #[must_use]
232 pub fn version(&self) -> String {
233 env!("CARGO_PKG_VERSION").to_owned()
234 }
235
236 #[must_use]
253 pub fn stats(&self) -> Stats {
254 self.stats.to_stats()
255 }
256
257 pub fn get(&self, key: &K) -> Result<Arc<V>, CacheError> {
278 let hashed_key = self.hash_key(key);
279
280 let result = match self.objects.get(&hashed_key) {
281 Some(object) if object.key_matches(key) && !object.is_expired() => {
282 self.stats.incr_hits();
283 Ok(object.data())
284 },
285
286 _ => {
287 self.stats.incr_misses();
288 Err(CacheError::KeyNotFound)
289 },
290 };
291
292 self.broadcast(WorkerEvent::Get(hashed_key, result.is_ok()))?;
293
294 result
295 }
296
297 pub fn set(&self, key: K, value: V, ttl: Option<u32>) -> Result<(), CacheError> {
317 let hashed_key = self.hash_key(&key);
318
319 let object = Object::new(key, value, ttl);
320 let base_size = self.overhead_manager.base_size(&object);
321 let expiry = object.expiry();
322
323 if base_size == 0 {
324 return Err(CacheError::ZeroValueSize);
325 }
326
327 if self.stats.exceeds_max_size(base_size) {
328 return Err(CacheError::ExceedingValueSize);
329 }
330
331 self.stats.incr_sets();
332
333 let old_object_info = self.objects
334 .insert(hashed_key, object)
335 .map(|old_object| {
336 let base_size = self.overhead_manager.base_size(&old_object);
337 let expiry = old_object.expiry();
338
339 (base_size, expiry)
340 });
341
342 let base_size_delta = if let Some((old_object_size, _)) = old_object_info {
343 base_size as i64 - old_object_size as i64
344 } else {
345 self.stats.incr_num_objects();
347 base_size as i64
348 };
349
350 self.stats.update_base_used_size(base_size_delta);
351 self.broadcast(WorkerEvent::Set(hashed_key, base_size, expiry, old_object_info))?;
352
353 Ok(())
354 }
355
356 pub fn del(&self, key: &K) -> Result<(), CacheError> {
376 let hashed_key = self.hash_key(key);
377
378 let (removed_hashed_key, object) = erase(
379 &self.objects,
380 &self.stats,
381 &self.overhead_manager,
382 Some(EraseKey::Original(key, hashed_key)),
383 )?;
384
385 self.stats.incr_dels();
386 self.broadcast(WorkerEvent::Del(removed_hashed_key, object.expiry()))?;
387
388 Ok(())
389 }
390
391 pub fn has(&self, key: &K) -> bool {
410 let hashed_key = self.hash_key(key);
411
412 self.objects
413 .get(&hashed_key)
414 .is_some_and(|object| object.key_matches(key) && !object.is_expired())
415 }
416
417 pub fn peek(&self, key: &K) -> Result<Arc<V>, CacheError> {
446 let hashed_key = self.hash_key(key);
447
448 match self.objects.get(&hashed_key) {
449 Some(object) if object.key_matches(key) && !object.is_expired() =>
450 Ok(object.data()),
451
452 _ => Err(CacheError::KeyNotFound),
453 }
454 }
455
456 pub fn ttl(&self, key: &K, ttl: Option<u32>) -> Result<(), CacheError> {
473 let hashed_key = self.hash_key(key);
474
475 let mut object = match self.objects.get_mut(&hashed_key) {
476 Some(object) if object.key_matches(key) && !object.is_expired() => object,
477 _ => return Err(CacheError::KeyNotFound),
478 };
479
480 let old_expiry = object.expiry();
481 let old_base_size = self.overhead_manager.base_size(&object);
482
483 object.expires(ttl);
484
485 let new_expiry = object.expiry();
486 let new_base_size = self.overhead_manager.base_size(&object);
487
488 self.stats.update_base_used_size(new_base_size as i64 - old_base_size as i64);
489 self.broadcast(WorkerEvent::Ttl(hashed_key, old_expiry, new_expiry))?;
490
491 Ok(())
492 }
493
494 pub fn size(&self, key: &K) -> Result<ObjectSize, CacheError> {
515 let hashed_key = self.hash_key(key);
516
517 match self.objects.get(&hashed_key) {
518 Some(object) if object.key_matches(key) && !object.is_expired() =>
519 Ok(self.overhead_manager.total_size(&object)),
520
521 _ => Err(CacheError::KeyNotFound),
522 }
523 }
524
525 pub fn wipe(&self) -> Result<(), CacheError> {
541 info!("Wiping cache");
542
543 self.objects.clear();
544 self.stats.clear();
545
546 self.broadcast(WorkerEvent::Wipe)?;
547
548 Ok(())
549 }
550
551 pub fn resize(&self, max_size: CacheSize) -> Result<(), CacheError> {
570 if max_size == 0 {
571 return Err(CacheError::ZeroCacheSize);
572 }
573
574 let current_max_size = self.stats.get_max_size();
575
576 if max_size == current_max_size {
577 return Ok(());
578 }
579
580 info!(
581 "Resizing cache from {} to {}",
582 fmt::memory(current_max_size, Some(2)),
583 fmt::memory(max_size, Some(2)),
584 );
585
586 self.stats.set_max_size(max_size);
587 self.broadcast(WorkerEvent::Resize(max_size))?;
588
589 Ok(())
590 }
591
592 pub fn policy(&self, policy: PaperPolicy) -> Result<(), CacheError> {
608 if !policy.is_auto() && !self.stats.get_policies().contains(&policy) {
609 return Err(CacheError::UnconfiguredPolicy);
610 }
611
612 self.stats.set_policy(policy)?;
613 self.broadcast(WorkerEvent::Policy(policy))?;
614
615 Ok(())
616 }
617
618 fn broadcast(&self, event: WorkerEvent) -> Result<(), CacheError> {
619 self.worker_manager
620 .try_send(event)
621 .map_err(|_| CacheError::Internal)?;
622
623 Ok(())
624 }
625
626 fn hash_key(&self, key: &K) -> HashedKey {
627 self.hasher.hash_one(key)
628 }
629}
630
631pub enum EraseKey<'a, K> {
632 Original(&'a K, HashedKey),
633 Hashed(HashedKey),
634}
635
636pub fn erase<K, V>(
637 objects: &ObjectMapRef<K, V>,
638 stats: &StatsRef,
639 overhead_manager: &OverheadManagerRef,
640 maybe_key: Option<EraseKey<K>>,
641) -> Result<(HashedKey, Object<K, V>), CacheError>
642where
643 K: Eq + TypeSize,
644 V: TypeSize,
645{
646 let hashed_key = match maybe_key {
647 Some(EraseKey::Original(_, hashed_key)) => hashed_key,
648 Some(EraseKey::Hashed(hashed_key)) => hashed_key,
649
650 None => {
651 objects
656 .iter()
657 .next()
658 .ok_or(CacheError::Internal)?
659 .key().to_owned()
660 },
661 };
662
663 let Entry::Occupied(entry) = objects.entry(hashed_key) else {
667 return Err(CacheError::KeyNotFound);
668 };
669
670 if let Some(EraseKey::Original(key, _)) = maybe_key && !entry.get().key_matches(key) {
671 return Err(CacheError::KeyNotFound);
672 };
673
674 let object = entry.remove();
675 let base_size = overhead_manager.base_size(&object) as i64;
676
677 stats.update_base_used_size(-base_size);
678 stats.decr_num_objects();
679
680 match !object.is_expired() {
681 true => Ok((hashed_key, object)),
682 false => Err(CacheError::KeyNotFound),
683 }
684}
685
686unsafe impl<K, V, S> Send for PaperCache<K, V, S> {}
687
688#[cfg(test)]
689mod tests {
690 use crate::{PaperCache, PaperPolicy, CacheError};
691
692 const TEST_CACHE_MAX_SIZE: u64 = 1000;
693
694 #[test]
695 fn it_returns_correct_version() {
696 let cache = init_test_cache();
697 assert_eq!(cache.version(), env!("CARGO_PKG_VERSION"));
698 }
699
700 #[test]
701 fn it_returns_stats() {
702 let cache = init_test_cache();
703 let stats = cache.stats();
704
705 assert_eq!(stats.get_max_size(), TEST_CACHE_MAX_SIZE);
706 }
707
708 #[test]
709 fn it_gets_an_existing_object() {
710 let cache = init_test_cache();
711
712 assert!(cache.set(0, 1, None).is_ok());
713 assert_eq!(cache.get(&0).as_deref(), Ok(&1));
714 }
715
716 #[test]
717 fn it_does_not_get_a_non_existing_object() {
718 let cache = init_test_cache();
719
720 assert!(cache.set(0, 1, None).is_ok());
721 assert_eq!(cache.get(&1), Err(CacheError::KeyNotFound));
722 }
723
724 #[test]
725 fn it_calculates_miss_ratio_correctly() {
726 let cache = init_test_cache();
727
728 assert!(cache.set(0, 1, None).is_ok());
729 assert!(cache.get(&0).is_ok());
730 assert!(cache.get(&0).is_ok());
731 assert!(cache.get(&0).is_ok());
732 assert!(cache.get(&1).is_err());
733
734 let stats = cache.stats();
735 assert_eq!(stats.get_miss_ratio(), 0.25);
736 }
737
738 #[test]
739 fn it_sets_with_no_ttl() {
740 let cache = init_test_cache();
741
742 assert!(cache.set(0, 1, None).is_ok());
743 assert!(cache.get(&0).is_ok());
744 }
745
746 #[test]
747 fn it_sets_with_ttl() {
748 use std::{
749 thread,
750 time::Duration,
751 };
752
753 let cache = init_test_cache();
754 assert!(cache.set(0, 1, Some(1)).is_ok());
755
756 assert!(cache.get(&0).is_ok());
757 thread::sleep(Duration::from_secs(2));
758 assert!(cache.get(&0).is_err());
759 }
760
761 #[test]
762 fn it_dels_an_existing_object() {
763 let cache = init_test_cache();
764 assert!(cache.set(0, 1, Some(1)).is_ok());
765
766 assert!(cache.get(&0).is_ok());
767 assert!(cache.del(&0).is_ok());
768 assert!(cache.get(&0).is_err());
769 }
770
771 #[test]
772 fn it_does_not_del_a_non_existing_object() {
773 let cache = init_test_cache();
774 assert_eq!(cache.del(&0), Err(CacheError::KeyNotFound));
775 }
776
777 #[test]
778 fn it_does_not_allow_empty_policies() {
779 let try_cache = PaperCache::<u32, u32>::new(
780 TEST_CACHE_MAX_SIZE,
781 &[],
782 PaperPolicy::Lfu,
783 );
784
785 assert!(try_cache.is_err_and(|err| err == CacheError::EmptyPolicies));
786
787 let try_cache = PaperCache::<u32, u32>::new(
788 TEST_CACHE_MAX_SIZE,
789 &[],
790 PaperPolicy::Auto,
791 );
792
793 assert!(try_cache.is_err_and(|err| err == CacheError::EmptyPolicies));
794 }
795
796 #[test]
797 fn it_does_not_allow_auto_policy_in_configured_policies() {
798 let try_cache = PaperCache::<u32, u32>::new(
799 TEST_CACHE_MAX_SIZE,
800 &[PaperPolicy::Auto],
801 PaperPolicy::Auto,
802 );
803
804 assert!(try_cache.is_err_and(|err| err == CacheError::ConfiguredAutoPolicy));
805
806 let try_cache = PaperCache::<u32, u32>::new(
807 TEST_CACHE_MAX_SIZE,
808 &[PaperPolicy::Auto, PaperPolicy::Lru],
809 PaperPolicy::Auto,
810 );
811
812 assert!(try_cache.is_err_and(|err| err == CacheError::ConfiguredAutoPolicy));
813 }
814
815 #[test]
816 fn it_does_not_allow_duplicate_policies() {
817 let try_cache = PaperCache::<u32, u32>::new(
818 TEST_CACHE_MAX_SIZE,
819 &[PaperPolicy::Lfu, PaperPolicy::Lru, PaperPolicy::Lfu],
820 PaperPolicy::Lfu,
821 );
822
823 assert!(try_cache.is_err_and(|err| err == CacheError::DuplicatePolicies));
824
825 let try_cache = PaperCache::<u32, u32>::new(
826 TEST_CACHE_MAX_SIZE,
827 &[PaperPolicy::Lfu, PaperPolicy::Lru],
828 PaperPolicy::Lfu,
829 );
830
831 assert!(try_cache.is_ok());
832 }
833
834 #[test]
835 fn it_has_an_existing_object() {
836 let cache = init_test_cache();
837
838 assert!(cache.set(0, 1, Some(1)).is_ok());
839 assert!(cache.has(&0));
840 }
841
842 #[test]
843 fn it_does_not_have_a_non_existing_object() {
844 let cache = init_test_cache();
845 assert!(!cache.has(&1));
846 }
847
848 #[test]
849 fn it_peeks_an_existing_object() {
850 let cache = init_test_cache();
851
852 assert!(cache.set(0, 1, None).is_ok());
853 assert_eq!(cache.peek(&0).as_deref(), Ok(&1));
854 }
855
856 #[test]
857 fn it_does_not_peek_a_non_existing_object() {
858 let cache = init_test_cache();
859
860 assert!(cache.set(0, 1, None).is_ok());
861 assert_eq!(cache.peek(&1), Err(CacheError::KeyNotFound));
862 }
863
864 #[test]
865 fn it_sets_an_existing_objects_ttl() {
866 use std::{
867 thread,
868 time::Duration,
869 };
870
871 let cache = init_test_cache();
872
873 assert!(cache.set(0, 1, None).is_ok());
874 assert!(cache.get(&0).is_ok());
875
876 assert!(cache.ttl(&0, Some(1)).is_ok());
877
878 thread::sleep(Duration::from_secs(2));
879 assert_eq!(cache.get(&0), Err(CacheError::KeyNotFound));
880 }
881
882 #[test]
883 fn it_does_not_set_a_non_existing_objects_ttl() {
884 let cache = init_test_cache();
885 assert_eq!(cache.ttl(&0, Some(1)), Err(CacheError::KeyNotFound));
886 }
887
888 #[test]
889 fn it_resets_an_objects_ttl() {
890 use std::{
891 thread,
892 time::Duration,
893 };
894
895 let cache = init_test_cache();
896
897 assert!(cache.set(0, 1, Some(1)).is_ok());
898 assert!(cache.get(&0).is_ok());
899
900 assert!(cache.ttl(&0, Some(5)).is_ok());
901
902 thread::sleep(Duration::from_secs(2));
903 assert!(cache.get(&0).is_ok());
904 }
905
906 #[test]
907 fn it_gets_an_objects_size() {
908 use std::mem;
909
910 use crate::object::{
911 ExpireTime,
912 overhead::get_policy_overhead,
913 };
914
915 let cache = init_test_cache();
916
917 let expected = 4 + 4
918 + mem::size_of::<ExpireTime>() as u32
919 + get_policy_overhead(&PaperPolicy::Lfu);
920
921 assert!(cache.set(0, 1, None).is_ok());
922 assert_eq!(cache.size(&0), Ok(expected));
923 }
924
925 #[test]
926 fn it_gets_an_expiring_objects_size() {
927 use std::mem;
928
929 use crate::object::{
930 ExpireTime,
931 overhead::{get_policy_overhead, get_ttl_overhead},
932 };
933
934 let cache = init_test_cache();
935
936 let expected = 4 + 4
937 + mem::size_of::<ExpireTime>() as u32
938 + get_policy_overhead(&PaperPolicy::Lfu)
939 + get_ttl_overhead();
940
941 assert!(cache.set(0, 1, Some(1)).is_ok());
942 assert_eq!(cache.size(&0), Ok(expected));
943 }
944
945 #[test]
946 fn it_gets_an_objects_size_after_policy_switch() {
947 use std::mem;
948
949 use crate::object::{
950 ExpireTime,
951 overhead::get_policy_overhead,
952 };
953
954 let cache = PaperCache::<u32, u32>::new(
955 TEST_CACHE_MAX_SIZE,
956 &[PaperPolicy::Lru, PaperPolicy::Lfu],
957 PaperPolicy::Lfu,
958 ).expect("Could not initialize test cache.");
959
960 let base_expected = 4 + 4 + mem::size_of::<ExpireTime>() as u32;
961 let lfu_expected = base_expected + get_policy_overhead(&PaperPolicy::Lfu);
962 let lru_expected = base_expected + get_policy_overhead(&PaperPolicy::Lru);
963
964 assert!(cache.set(0, 1, None).is_ok());
965 assert_eq!(cache.size(&0), Ok(lfu_expected));
966
967 assert!(cache.policy(PaperPolicy::Lru).is_ok());
968 assert_eq!(cache.size(&0), Ok(lru_expected));
969 }
970
971 #[test]
972 fn it_gets_an_objects_size_after_set_ttl() {
973 use std::mem;
974
975 use crate::object::{
976 ExpireTime,
977 overhead::{get_policy_overhead, get_ttl_overhead},
978 };
979
980 let cache = init_test_cache();
981
982 let pre_expected = 4 + 4
983 + mem::size_of::<ExpireTime>() as u32
984 + get_policy_overhead(&PaperPolicy::Lfu);
985
986 let post_expected = pre_expected + get_ttl_overhead();
987
988 assert!(cache.set(0, 1, None).is_ok());
989 assert_eq!(cache.size(&0), Ok(pre_expected));
990
991 assert!(cache.ttl(&0, Some(1)).is_ok());
992 assert_eq!(cache.size(&0), Ok(post_expected));
993 }
994
995 #[test]
996 fn it_gets_an_objects_size_after_unset_ttl() {
997 use std::mem;
998
999 use crate::object::{
1000 ExpireTime,
1001 overhead::{get_policy_overhead, get_ttl_overhead},
1002 };
1003
1004 let cache = init_test_cache();
1005
1006 let pre_expected = 4 + 4
1007 + mem::size_of::<ExpireTime>() as u32
1008 + get_policy_overhead(&PaperPolicy::Lfu)
1009 + get_ttl_overhead();
1010
1011 let post_expected = pre_expected - get_ttl_overhead();
1012
1013 assert!(cache.set(0, 1, Some(1)).is_ok());
1014 assert_eq!(cache.size(&0), Ok(pre_expected));
1015
1016 assert!(cache.ttl(&0, None).is_ok());
1017 assert_eq!(cache.size(&0), Ok(post_expected));
1018 }
1019
1020 #[test]
1021 fn stats_show_correct_used_size() {
1022 use std::mem;
1023
1024 use crate::object::{
1025 ExpireTime,
1026 overhead::{get_policy_overhead, get_ttl_overhead},
1027 };
1028
1029 let cache = init_test_cache();
1030
1031 let expected = (4 + 4) * 2
1032 + mem::size_of::<ExpireTime>() as u32 * 2
1033 + get_policy_overhead(&PaperPolicy::Lfu) * 2
1034 + get_ttl_overhead();
1035
1036 assert!(cache.set(0, 1, None).is_ok());
1037 assert!(cache.set(1, 1, Some(1)).is_ok());
1038
1039 let stats = cache.stats();
1040 assert_eq!(stats.get_used_size(), expected as u64);
1041 }
1042
1043 #[test]
1044 fn stats_show_correct_used_size_after_policy_switch() {
1045 use std::mem;
1046
1047 use crate::object::{
1048 ExpireTime,
1049 overhead::get_policy_overhead,
1050 };
1051
1052 let cache = PaperCache::<u32, u32>::new(
1053 TEST_CACHE_MAX_SIZE,
1054 &[PaperPolicy::Lru, PaperPolicy::Lfu],
1055 PaperPolicy::Lfu,
1056 ).expect("Could not initialize test cache.");
1057
1058 let base_expected = 4 + 4 + mem::size_of::<ExpireTime>() as u32;
1059 let lfu_expected = base_expected + get_policy_overhead(&PaperPolicy::Lfu);
1060 let lru_expected = base_expected + get_policy_overhead(&PaperPolicy::Lru);
1061
1062 assert!(cache.set(0, 1, None).is_ok());
1063 let stats = cache.stats();
1064 assert_eq!(stats.get_used_size(), lfu_expected as u64);
1065
1066 assert!(cache.policy(PaperPolicy::Lru).is_ok());
1067 let stats = cache.stats();
1068 assert_eq!(stats.get_used_size(), lru_expected as u64);
1069 }
1070
1071 #[test]
1072 fn stats_show_correct_used_size_after_set_ttl() {
1073 use std::mem;
1074
1075 use crate::object::{
1076 ExpireTime,
1077 overhead::{get_policy_overhead, get_ttl_overhead},
1078 };
1079
1080 let cache = init_test_cache();
1081
1082 let pre_expected = 4 + 4
1083 + mem::size_of::<ExpireTime>() as u32
1084 + get_policy_overhead(&PaperPolicy::Lfu);
1085
1086 let post_expected = pre_expected + get_ttl_overhead();
1087
1088 assert!(cache.set(0, 1, None).is_ok());
1089 let stats = cache.stats();
1090 assert_eq!(stats.get_used_size(), pre_expected as u64);
1091
1092 assert!(cache.ttl(&0, Some(1)).is_ok());
1093 let stats = cache.stats();
1094 assert_eq!(stats.get_used_size(), post_expected as u64);
1095 }
1096
1097 #[test]
1098 fn stats_show_correct_used_size_after_unset_ttl() {
1099 use std::mem;
1100
1101 use crate::object::{
1102 ExpireTime,
1103 overhead::{get_policy_overhead, get_ttl_overhead},
1104 };
1105
1106 let cache = init_test_cache();
1107
1108 let pre_expected = 4 + 4
1109 + mem::size_of::<ExpireTime>() as u32
1110 + get_policy_overhead(&PaperPolicy::Lfu)
1111 + get_ttl_overhead();
1112
1113 let post_expected = pre_expected - get_ttl_overhead();
1114
1115 assert!(cache.set(0, 1, Some(1)).is_ok());
1116 let stats = cache.stats();
1117 assert_eq!(stats.get_used_size(), pre_expected as u64);
1118
1119 assert!(cache.ttl(&0, None).is_ok());
1120 let stats = cache.stats();
1121 assert_eq!(stats.get_used_size(), post_expected as u64);
1122 }
1123
1124 fn init_test_cache() -> PaperCache<u32, u32> {
1125 PaperCache::<u32, u32>::new(
1126 TEST_CACHE_MAX_SIZE,
1127 &[PaperPolicy::Lfu],
1128 PaperPolicy::Lfu,
1129 ).expect("Could not initialize test cache.")
1130 }
1131}