1mod error;
9mod worker;
10mod object;
11mod policy;
12mod status;
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, error};
37
38use kwik::{
39 fmt,
40 math::set::Multiset,
41};
42
43use crate::{
44 status::{AtomicStatus, Status},
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 StatusRef = Arc<AtomicStatus>;
71pub type OverheadManagerRef = Arc<OverheadManager>;
72
73pub struct PaperCache<K, V, S = RandomState> {
74 objects: ObjectMapRef<K, V>,
75 status: StatusRef,
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 status = Arc::new(AtomicStatus::new(max_size, policies, policy)?);
191 let overhead_manager = Arc::new(OverheadManager::new(&status));
192
193 let (worker_sender, worker_listener) = unbounded();
194
195 let mut worker_manager = WorkerManager::new(
196 worker_listener,
197 &objects,
198 &status,
199 &overhead_manager,
200 )?;
201
202 thread::spawn(move || worker_manager.run());
203
204 let cache = PaperCache {
205 objects,
206 status,
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 pub fn status(&self) -> Result<Status, CacheError> {
254 self.status.try_to_status()
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.status.incr_hits();
283 Ok(object.data())
284 },
285
286 _ => {
287 self.status.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.status.exceeds_max_size(base_size) {
328 return Err(CacheError::ExceedingValueSize);
329 }
330
331 self.status.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.status.incr_num_objects();
347 base_size as i64
348 };
349
350 self.status.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.status,
381 &self.overhead_manager,
382 Some(EraseKey::Original(key, hashed_key)),
383 )?;
384
385 self.status.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.status.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.status.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.status.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.status.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.status.policies().contains(&policy) {
609 return Err(CacheError::UnconfiguredPolicy);
610 }
611
612 self.status.set_policy(policy)?;
613 self.broadcast(WorkerEvent::Policy(policy))?;
614
615 Ok(())
616 }
617
618 fn broadcast(&self, event: WorkerEvent) -> Result<(), CacheError> {
619 if let Err(err) = self.worker_manager.try_send(event) {
620 error!("Could not communicate with workers: {err:?}");
621 return Err(CacheError::Internal);
622 }
623
624 Ok(())
625 }
626
627 fn hash_key(&self, key: &K) -> HashedKey {
628 self.hasher.hash_one(key)
629 }
630}
631
632pub enum EraseKey<'a, K> {
633 Original(&'a K, HashedKey),
634 Hashed(HashedKey),
635}
636
637pub fn erase<K, V>(
638 objects: &ObjectMapRef<K, V>,
639 status: &StatusRef,
640 overhead_manager: &OverheadManagerRef,
641 maybe_key: Option<EraseKey<K>>,
642) -> Result<(HashedKey, Object<K, V>), CacheError>
643where
644 K: Eq + TypeSize,
645 V: TypeSize,
646{
647 let hashed_key = match maybe_key {
648 Some(EraseKey::Original(_, hashed_key)) => hashed_key,
649 Some(EraseKey::Hashed(hashed_key)) => hashed_key,
650
651 None => {
652 let Some(object) = objects.iter().next() else {
657 error!("Object store is empty with non-zero used size");
658 return Err(CacheError::Internal);
659 };
660
661 object.key().to_owned()
662 },
663 };
664
665 let Entry::Occupied(entry) = objects.entry(hashed_key) else {
669 return Err(CacheError::KeyNotFound);
670 };
671
672 if let Some(EraseKey::Original(key, _)) = maybe_key && !entry.get().key_matches(key) {
673 return Err(CacheError::KeyNotFound);
674 };
675
676 let object = entry.remove();
677 let base_size = overhead_manager.base_size(&object) as i64;
678
679 status.update_base_used_size(-base_size);
680 status.decr_num_objects();
681
682 match !object.is_expired() {
683 true => Ok((hashed_key, object)),
684 false => Err(CacheError::KeyNotFound),
685 }
686}
687
688unsafe impl<K, V, S> Send for PaperCache<K, V, S> {}
689
690#[cfg(test)]
691mod tests {
692 use crate::{PaperCache, PaperPolicy, CacheError};
693
694 const TEST_CACHE_MAX_SIZE: u64 = 1000;
695
696 #[test]
697 fn it_returns_correct_version() {
698 let cache = init_test_cache();
699 assert_eq!(cache.version(), env!("CARGO_PKG_VERSION"));
700 }
701
702 #[test]
703 fn it_returns_status() {
704 let cache = init_test_cache();
705 let status = cache.status().unwrap();
706
707 assert_eq!(status.max_size(), TEST_CACHE_MAX_SIZE);
708 }
709
710 #[test]
711 fn it_gets_an_existing_object() {
712 let cache = init_test_cache();
713
714 assert!(cache.set(0, 1, None).is_ok());
715 assert_eq!(cache.get(&0).as_deref(), Ok(&1));
716 }
717
718 #[test]
719 fn it_does_not_get_a_non_existing_object() {
720 let cache = init_test_cache();
721
722 assert!(cache.set(0, 1, None).is_ok());
723 assert_eq!(cache.get(&1), Err(CacheError::KeyNotFound));
724 }
725
726 #[test]
727 fn it_calculates_miss_ratio_correctly() {
728 let cache = init_test_cache();
729
730 assert!(cache.set(0, 1, None).is_ok());
731 assert!(cache.get(&0).is_ok());
732 assert!(cache.get(&0).is_ok());
733 assert!(cache.get(&0).is_ok());
734 assert!(cache.get(&1).is_err());
735
736 let status = cache.status().unwrap();
737 assert_eq!(status.miss_ratio(), 0.25);
738 }
739
740 #[test]
741 fn it_sets_with_no_ttl() {
742 let cache = init_test_cache();
743
744 assert!(cache.set(0, 1, None).is_ok());
745 assert!(cache.get(&0).is_ok());
746 }
747
748 #[test]
749 fn it_sets_with_ttl() {
750 use std::{
751 thread,
752 time::Duration,
753 };
754
755 let cache = init_test_cache();
756 assert!(cache.set(0, 1, Some(1)).is_ok());
757
758 assert!(cache.get(&0).is_ok());
759 thread::sleep(Duration::from_secs(2));
760 assert!(cache.get(&0).is_err());
761 }
762
763 #[test]
764 fn it_dels_an_existing_object() {
765 let cache = init_test_cache();
766 assert!(cache.set(0, 1, Some(1)).is_ok());
767
768 assert!(cache.get(&0).is_ok());
769 assert!(cache.del(&0).is_ok());
770 assert!(cache.get(&0).is_err());
771 }
772
773 #[test]
774 fn it_does_not_del_a_non_existing_object() {
775 let cache = init_test_cache();
776 assert_eq!(cache.del(&0), Err(CacheError::KeyNotFound));
777 }
778
779 #[test]
780 fn it_does_not_allow_empty_policies() {
781 let try_cache = PaperCache::<u32, u32>::new(
782 TEST_CACHE_MAX_SIZE,
783 &[],
784 PaperPolicy::Lfu,
785 );
786
787 assert!(try_cache.is_err_and(|err| err == CacheError::EmptyPolicies));
788
789 let try_cache = PaperCache::<u32, u32>::new(
790 TEST_CACHE_MAX_SIZE,
791 &[],
792 PaperPolicy::Auto,
793 );
794
795 assert!(try_cache.is_err_and(|err| err == CacheError::EmptyPolicies));
796 }
797
798 #[test]
799 fn it_does_not_allow_auto_policy_in_configured_policies() {
800 let try_cache = PaperCache::<u32, u32>::new(
801 TEST_CACHE_MAX_SIZE,
802 &[PaperPolicy::Auto],
803 PaperPolicy::Auto,
804 );
805
806 assert!(try_cache.is_err_and(|err| err == CacheError::ConfiguredAutoPolicy));
807
808 let try_cache = PaperCache::<u32, u32>::new(
809 TEST_CACHE_MAX_SIZE,
810 &[PaperPolicy::Auto, PaperPolicy::Lru],
811 PaperPolicy::Auto,
812 );
813
814 assert!(try_cache.is_err_and(|err| err == CacheError::ConfiguredAutoPolicy));
815 }
816
817 #[test]
818 fn it_does_not_allow_duplicate_policies() {
819 let try_cache = PaperCache::<u32, u32>::new(
820 TEST_CACHE_MAX_SIZE,
821 &[PaperPolicy::Lfu, PaperPolicy::Lru, PaperPolicy::Lfu],
822 PaperPolicy::Lfu,
823 );
824
825 assert!(try_cache.is_err_and(|err| err == CacheError::DuplicatePolicies));
826
827 let try_cache = PaperCache::<u32, u32>::new(
828 TEST_CACHE_MAX_SIZE,
829 &[PaperPolicy::Lfu, PaperPolicy::Lru],
830 PaperPolicy::Lfu,
831 );
832
833 assert!(try_cache.is_ok());
834 }
835
836 #[test]
837 fn it_has_an_existing_object() {
838 let cache = init_test_cache();
839
840 assert!(cache.set(0, 1, Some(1)).is_ok());
841 assert!(cache.has(&0));
842 }
843
844 #[test]
845 fn it_does_not_have_a_non_existing_object() {
846 let cache = init_test_cache();
847 assert!(!cache.has(&1));
848 }
849
850 #[test]
851 fn it_peeks_an_existing_object() {
852 let cache = init_test_cache();
853
854 assert!(cache.set(0, 1, None).is_ok());
855 assert_eq!(cache.peek(&0).as_deref(), Ok(&1));
856 }
857
858 #[test]
859 fn it_does_not_peek_a_non_existing_object() {
860 let cache = init_test_cache();
861
862 assert!(cache.set(0, 1, None).is_ok());
863 assert_eq!(cache.peek(&1), Err(CacheError::KeyNotFound));
864 }
865
866 #[test]
867 fn it_sets_an_existing_objects_ttl() {
868 use std::{
869 thread,
870 time::Duration,
871 };
872
873 let cache = init_test_cache();
874
875 assert!(cache.set(0, 1, None).is_ok());
876 assert!(cache.get(&0).is_ok());
877
878 assert!(cache.ttl(&0, Some(1)).is_ok());
879
880 thread::sleep(Duration::from_secs(2));
881 assert_eq!(cache.get(&0), Err(CacheError::KeyNotFound));
882 }
883
884 #[test]
885 fn it_does_not_set_a_non_existing_objects_ttl() {
886 let cache = init_test_cache();
887 assert_eq!(cache.ttl(&0, Some(1)), Err(CacheError::KeyNotFound));
888 }
889
890 #[test]
891 fn it_resets_an_objects_ttl() {
892 use std::{
893 thread,
894 time::Duration,
895 };
896
897 let cache = init_test_cache();
898
899 assert!(cache.set(0, 1, Some(1)).is_ok());
900 assert!(cache.get(&0).is_ok());
901
902 assert!(cache.ttl(&0, Some(5)).is_ok());
903
904 thread::sleep(Duration::from_secs(2));
905 assert!(cache.get(&0).is_ok());
906 }
907
908 #[test]
909 fn it_gets_an_objects_size() {
910 use std::mem;
911
912 use crate::object::{
913 ExpireTime,
914 overhead::get_policy_overhead,
915 };
916
917 let cache = init_test_cache();
918
919 let expected = 4 + 4
920 + mem::size_of::<ExpireTime>() as u32
921 + get_policy_overhead(&PaperPolicy::Lfu);
922
923 assert!(cache.set(0, 1, None).is_ok());
924 assert_eq!(cache.size(&0), Ok(expected));
925 }
926
927 #[test]
928 fn it_gets_an_expiring_objects_size() {
929 use std::mem;
930
931 use crate::object::{
932 ExpireTime,
933 overhead::{get_policy_overhead, get_ttl_overhead},
934 };
935
936 let cache = init_test_cache();
937
938 let expected = 4 + 4
939 + mem::size_of::<ExpireTime>() as u32
940 + get_policy_overhead(&PaperPolicy::Lfu)
941 + get_ttl_overhead();
942
943 assert!(cache.set(0, 1, Some(1)).is_ok());
944 assert_eq!(cache.size(&0), Ok(expected));
945 }
946
947 #[test]
948 fn it_gets_an_objects_size_after_policy_switch() {
949 use std::mem;
950
951 use crate::object::{
952 ExpireTime,
953 overhead::get_policy_overhead,
954 };
955
956 let cache = PaperCache::<u32, u32>::new(
957 TEST_CACHE_MAX_SIZE,
958 &[PaperPolicy::Lru, PaperPolicy::Lfu],
959 PaperPolicy::Lfu,
960 ).expect("Could not initialize test cache");
961
962 let base_expected = 4 + 4 + mem::size_of::<ExpireTime>() as u32;
963 let lfu_expected = base_expected + get_policy_overhead(&PaperPolicy::Lfu);
964 let lru_expected = base_expected + get_policy_overhead(&PaperPolicy::Lru);
965
966 assert!(cache.set(0, 1, None).is_ok());
967 assert_eq!(cache.size(&0), Ok(lfu_expected));
968
969 assert!(cache.policy(PaperPolicy::Lru).is_ok());
970 assert_eq!(cache.size(&0), Ok(lru_expected));
971 }
972
973 #[test]
974 fn it_gets_an_objects_size_after_set_ttl() {
975 use std::mem;
976
977 use crate::object::{
978 ExpireTime,
979 overhead::{get_policy_overhead, get_ttl_overhead},
980 };
981
982 let cache = init_test_cache();
983
984 let pre_expected = 4 + 4
985 + mem::size_of::<ExpireTime>() as u32
986 + get_policy_overhead(&PaperPolicy::Lfu);
987
988 let post_expected = pre_expected + get_ttl_overhead();
989
990 assert!(cache.set(0, 1, None).is_ok());
991 assert_eq!(cache.size(&0), Ok(pre_expected));
992
993 assert!(cache.ttl(&0, Some(1)).is_ok());
994 assert_eq!(cache.size(&0), Ok(post_expected));
995 }
996
997 #[test]
998 fn it_gets_an_objects_size_after_unset_ttl() {
999 use std::mem;
1000
1001 use crate::object::{
1002 ExpireTime,
1003 overhead::{get_policy_overhead, get_ttl_overhead},
1004 };
1005
1006 let cache = init_test_cache();
1007
1008 let pre_expected = 4 + 4
1009 + mem::size_of::<ExpireTime>() as u32
1010 + get_policy_overhead(&PaperPolicy::Lfu)
1011 + get_ttl_overhead();
1012
1013 let post_expected = pre_expected - get_ttl_overhead();
1014
1015 assert!(cache.set(0, 1, Some(1)).is_ok());
1016 assert_eq!(cache.size(&0), Ok(pre_expected));
1017
1018 assert!(cache.ttl(&0, None).is_ok());
1019 assert_eq!(cache.size(&0), Ok(post_expected));
1020 }
1021
1022 #[test]
1023 fn status_shows_correct_used_size() {
1024 use std::mem;
1025
1026 use crate::object::{
1027 ExpireTime,
1028 overhead::{get_policy_overhead, get_ttl_overhead},
1029 };
1030
1031 let cache = init_test_cache();
1032
1033 let expected = (4 + 4) * 2
1034 + mem::size_of::<ExpireTime>() as u32 * 2
1035 + get_policy_overhead(&PaperPolicy::Lfu) * 2
1036 + get_ttl_overhead();
1037
1038 assert!(cache.set(0, 1, None).is_ok());
1039 assert!(cache.set(1, 1, Some(1)).is_ok());
1040
1041 let status = cache.status().unwrap();
1042 assert_eq!(status.used_size(), expected as u64);
1043 }
1044
1045 #[test]
1046 fn status_shows_correct_used_size_after_policy_switch() {
1047 use std::mem;
1048
1049 use crate::object::{
1050 ExpireTime,
1051 overhead::get_policy_overhead,
1052 };
1053
1054 let cache = PaperCache::<u32, u32>::new(
1055 TEST_CACHE_MAX_SIZE,
1056 &[PaperPolicy::Lru, PaperPolicy::Lfu],
1057 PaperPolicy::Lfu,
1058 ).expect("Could not initialize test cache");
1059
1060 let base_expected = 4 + 4 + mem::size_of::<ExpireTime>() as u32;
1061 let lfu_expected = base_expected + get_policy_overhead(&PaperPolicy::Lfu);
1062 let lru_expected = base_expected + get_policy_overhead(&PaperPolicy::Lru);
1063
1064 assert!(cache.set(0, 1, None).is_ok());
1065 let status = cache.status().unwrap();
1066 assert_eq!(status.used_size(), lfu_expected as u64);
1067
1068 assert!(cache.policy(PaperPolicy::Lru).is_ok());
1069 let status = cache.status().unwrap();
1070 assert_eq!(status.used_size(), lru_expected as u64);
1071 }
1072
1073 #[test]
1074 fn status_shows_correct_used_size_after_set_ttl() {
1075 use std::mem;
1076
1077 use crate::object::{
1078 ExpireTime,
1079 overhead::{get_policy_overhead, get_ttl_overhead},
1080 };
1081
1082 let cache = init_test_cache();
1083
1084 let pre_expected = 4 + 4
1085 + mem::size_of::<ExpireTime>() as u32
1086 + get_policy_overhead(&PaperPolicy::Lfu);
1087
1088 let post_expected = pre_expected + get_ttl_overhead();
1089
1090 assert!(cache.set(0, 1, None).is_ok());
1091 let status = cache.status().unwrap();
1092 assert_eq!(status.used_size(), pre_expected as u64);
1093
1094 assert!(cache.ttl(&0, Some(1)).is_ok());
1095 let status = cache.status().unwrap();
1096 assert_eq!(status.used_size(), post_expected as u64);
1097 }
1098
1099 #[test]
1100 fn status_shows_correct_used_size_after_unset_ttl() {
1101 use std::mem;
1102
1103 use crate::object::{
1104 ExpireTime,
1105 overhead::{get_policy_overhead, get_ttl_overhead},
1106 };
1107
1108 let cache = init_test_cache();
1109
1110 let pre_expected = 4 + 4
1111 + mem::size_of::<ExpireTime>() as u32
1112 + get_policy_overhead(&PaperPolicy::Lfu)
1113 + get_ttl_overhead();
1114
1115 let post_expected = pre_expected - get_ttl_overhead();
1116
1117 assert!(cache.set(0, 1, Some(1)).is_ok());
1118 let status = cache.status().unwrap();
1119 assert_eq!(status.used_size(), pre_expected as u64);
1120
1121 assert!(cache.ttl(&0, None).is_ok());
1122 let status = cache.status().unwrap();
1123 assert_eq!(status.used_size(), post_expected as u64);
1124 }
1125
1126 fn init_test_cache() -> PaperCache<u32, u32> {
1127 PaperCache::<u32, u32>::new(
1128 TEST_CACHE_MAX_SIZE,
1129 &[PaperPolicy::Lfu],
1130 PaperPolicy::Lfu,
1131 ).expect("Could not initialize test cache")
1132 }
1133}