paper_cache/
lib.rs

1/*
2 * Copyright (c) Kia Shakiba
3 *
4 * This source code is licensed under the GNU AGPLv3 license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8mod 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	/// Creates an empty `PaperCache` with maximum size `max_size` and
90	/// eviction policy `policy`. If the maximum size is zero, a
91	/// [`CacheError`] will be returned.
92	///
93	/// # Examples
94	///
95	/// ```
96	/// use paper_cache::{PaperCache, PaperPolicy};
97	///
98	/// let cache = PaperCache::<u32, u32>::new(
99	///     1000,
100	///     &[PaperPolicy::Lfu],
101	///     PaperPolicy::Lfu,
102	/// );
103	///
104	/// assert!(cache.is_ok());
105	///
106	/// // Supplying a maximum size of zero will return a `CacheError`.
107	/// let cache = PaperCache::<u32, u32>::new(
108	///     0,
109	///     &[PaperPolicy::Lfu],
110	///     PaperPolicy::Lfu,
111	/// );
112	///
113	/// assert!(cache.is_err());
114	///
115	/// // Supplying duplicate policies will return a `CacheError`.
116	/// let cache = PaperCache::<u32, u32>::new(
117	///     1000,
118	///     &[PaperPolicy::Lfu, PaperPolicy::Lru, PaperPolicy::Lfu],
119	///     PaperPolicy::Lfu,
120	/// );
121	///
122	/// assert!(cache.is_err());
123	///
124	/// // Supplying a non-configured policy will return a `CacheError`.
125	/// let cache = PaperCache::<u32, u32>::new(
126	///     1000,
127	///     &[PaperPolicy::Lfu],
128	///     PaperPolicy::Lru,
129	/// );
130	///
131	/// assert!(cache.is_err());
132	/// ```
133	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	/// Creates an empty `PaperCache` with the supplied hasher.
147	///
148	/// # Examples
149	///
150	/// ```
151	/// use std::hash::RandomState;
152	/// use paper_cache::{PaperCache, PaperPolicy};
153	///
154	/// let cache = PaperCache::<u32, u32>::with_hasher(
155	///     1000,
156	///     &[PaperPolicy::Lfu],
157	///     PaperPolicy::Lfu,
158	///     RandomState::default(),
159	/// );
160	///
161	/// assert!(cache.is_ok());
162	/// ```
163	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	/// Returns the current cache version.
218	///
219	/// # Examples
220	/// ```
221	/// use paper_cache::{PaperCache, PaperPolicy};
222	///
223	/// let mut cache = PaperCache::<u32, u32>::new(
224	///     1000,
225	///     &[PaperPolicy::Lfu],
226	///     PaperPolicy::Lfu
227	/// ).unwrap();
228	///
229	/// assert_eq!(cache.version(), env!("CARGO_PKG_VERSION"));
230	/// ```
231	#[must_use]
232	pub fn version(&self) -> String {
233		env!("CARGO_PKG_VERSION").to_owned()
234	}
235
236	/// Returns the current statistics.
237	///
238	/// # Examples
239	/// ```
240	/// use paper_cache::{PaperCache, PaperPolicy};
241	///
242	/// let mut cache = PaperCache::<u32, u32>::new(
243	///     1000,
244	///     &[PaperPolicy::Lfu],
245	///     PaperPolicy::Lfu,
246	/// ).unwrap();
247	///
248	/// cache.set(0, 0, None);
249	///
250	/// assert!(cache.stats().get_used_size() > 0);
251	/// ```
252	#[must_use]
253	pub fn stats(&self) -> Stats {
254		self.stats.to_stats()
255	}
256
257	/// Gets the value associated with the supplied key.
258	/// If the key was not found in the cache, returns a [`CacheError`].
259	///
260	/// # Examples
261	/// ```
262	/// use paper_cache::{PaperCache, PaperPolicy};
263	///
264	/// let mut cache = PaperCache::<u32, u32>::new(
265	///     1000,
266	///     &[PaperPolicy::Lfu],
267	///     PaperPolicy::Lfu,
268	/// ).unwrap();
269	///
270	/// cache.set(0, 0, None);
271	///
272	/// // Getting a key which exists in the cache will return the associated value.
273	/// assert!(cache.get(&0).is_ok());
274	/// // Getting a key which does not exist in the cache will return a CacheError.
275	/// assert!(cache.get(&1).is_err());
276	/// ```
277	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	/// Sets the supplied key and value in the cache.
298	/// Returns a [`CacheError`] if the value size is zero or larger than
299	/// the cache's maximum size.
300	///
301	/// If the key already exists in the cache, the associated value is updated
302	/// to the supplied value.
303	///
304	/// # Examples
305	/// ```
306	/// use paper_cache::{PaperCache, PaperPolicy};
307	///
308	/// let mut cache = PaperCache::<u32, u32>::new(
309	///     1000,
310	///     &[PaperPolicy::Lfu],
311	///     PaperPolicy::Lfu,
312	/// ).unwrap();
313	///
314	/// assert!(cache.set(0, 0, None).is_ok());
315	/// ```
316	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			// the object is new, so increase the number of objects count
346			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	/// Deletes the object associated with the supplied key in the cache.
357	/// Returns a [`CacheError`] if the key was not found in the cache.
358	///
359	/// # Examples
360	/// ```
361	/// use paper_cache::{PaperCache, PaperPolicy};
362	///
363	/// let mut cache = PaperCache::<u32, u32>::new(
364	///     1000,
365	///     &[PaperPolicy::Lfu],
366	///     PaperPolicy::Lfu,
367	/// ).unwrap();
368	///
369	/// cache.set(0, 0, None);
370	/// assert!(cache.del(&0).is_ok());
371	///
372	/// // Deleting a key which does not exist in the cache will return a CacheError.
373	/// assert!(cache.del(&1).is_err());
374	/// ```
375	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	/// Checks if an object with the supplied key exists in the cache without
392	/// altering any of the cache's internal queues.
393	///
394	/// # Examples
395	/// ```
396	/// use paper_cache::{PaperCache, PaperPolicy};
397	///
398	/// let mut cache = PaperCache::<u32, u32>::new(
399	///     1000,
400	///     &[PaperPolicy::Lfu],
401	///     PaperPolicy::Lfu,
402	/// ).unwrap();
403	///
404	/// cache.set(0, 0, None);
405	///
406	/// assert!(cache.has(&0));
407	/// assert!(!cache.has(&1));
408	/// ```
409	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	/// Gets (peeks) the value associated with the supplied key without altering
418	/// any of the cache's internal queues.
419	/// If the key was not found in the cache, returns a [`CacheError`].
420	///
421	/// # Examples
422	/// ```
423	/// use paper_cache::{PaperCache, PaperPolicy};
424	///
425	/// let mut cache = PaperCache::<u32, u32>::new(
426	///     1000,
427	///     &[PaperPolicy::Lfu],
428	///     PaperPolicy::Lfu,
429	/// ).unwrap();
430	///
431	/// cache.set(0, 0, None);
432	/// cache.set(1, 0, None);
433	///
434	/// // Peeking a key which exists in the cache will return the associated value.
435	/// assert!(cache.peek(&0).is_ok());
436	/// // Peeking a key which does not exist in the cache will return a CacheError.
437	/// assert!(cache.peek(&2).is_err());
438	///
439	/// cache.set(2, 0, None);
440	///
441	/// // Peeking a key will not alter the eviction order of the objects.
442	/// assert!(cache.peek(&1).is_ok());
443	/// assert!(cache.peek(&2).is_ok());
444	/// ```
445	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	/// Sets the TTL associated with the supplied key.
457	/// If the key was not found in the cache, returns a [`CacheError`].
458	///
459	/// # Examples
460	/// ```
461	/// use paper_cache::{PaperCache, PaperPolicy};
462	///
463	/// let mut cache = PaperCache::<u32, u32>::new(
464	///     1000,
465	///     &[PaperPolicy::Lfu],
466	///     PaperPolicy::Lfu,
467	/// ).unwrap();
468	///
469	/// cache.set(0, 0, None); // value will not expire
470	/// cache.ttl(&0, Some(5)); // value will expire in 5 seconds
471	/// ```
472	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	/// Gets the size of the value associated with the supplied key in bytes.
495	/// If the key was not found in the cache, returns a [`CacheError`].
496	///
497	/// # Examples
498	/// ```
499	/// use paper_cache::{PaperCache, PaperPolicy};
500	///
501	/// let mut cache = PaperCache::<u32, u32>::new(
502	///     1000,
503	///     &[PaperPolicy::Lfu],
504	///     PaperPolicy::Lfu,
505	/// ).unwrap();
506	///
507	/// cache.set(0, 0, None);
508	///
509	/// // Sizing a key which exists in the cache will return the size of the associated value.
510	/// assert!(cache.size(&0).is_ok());
511	/// // Sizing a key which does not exist in the cache will return a CacheError.
512	/// assert!(cache.size(&1).is_err());
513	/// ```
514	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	/// Deletes all objects in the cache and sets the cache's used size to zero.
526	/// Returns a [`CacheError`] if the objects could not be wiped.
527	///
528	/// # Examples
529	/// ```
530	/// use paper_cache::{PaperCache, PaperPolicy};
531	///
532	/// let mut cache = PaperCache::<u32, u32>::new(
533	///     1000,
534	///     &[PaperPolicy::Lfu],
535	///     PaperPolicy::Lfu,
536	/// ).unwrap();
537	///
538	/// cache.wipe();
539	/// ```
540	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	/// Resizes the cache to the supplied maximum size.
552	/// If the supplied size is zero, returns a [`CacheError`].
553	///
554	/// # Examples
555	/// ```
556	/// use paper_cache::{PaperCache, PaperPolicy};
557	///
558	/// let mut cache = PaperCache::<u32, u32>::new(
559	///     1000,
560	///     &[PaperPolicy::Lfu],
561	///     PaperPolicy::Lfu,
562	/// ).unwrap();
563	///
564	/// assert!(cache.resize(1).is_ok());
565	///
566	/// // Resizing to a size of zero will return a CacheError.
567	/// assert!(cache.resize(0).is_err());
568	/// ```
569	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	/// Sets the eviction policy of the cache to the supplied policy.
593	///
594	/// # Examples
595	/// ```
596	/// use paper_cache::{PaperCache, PaperPolicy};
597	///
598	/// let mut cache = PaperCache::<u32, u32>::new(
599	///     1000,
600	///     &[PaperPolicy::Lfu],
601	///     PaperPolicy::Lfu,
602	/// ).unwrap();
603	///
604	/// assert!(cache.policy(PaperPolicy::Lfu).is_ok());
605	/// assert!(cache.policy(PaperPolicy::Lru).is_err());
606	/// ```
607	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			// the policy has run out of keys to evict (either it's a mini stack or
652			// something went wrong during policy reconstruction) so we fall back
653			// to evicting a random object
654
655			objects
656				.iter()
657				.next()
658				.ok_or(CacheError::Internal)?
659				.key().to_owned()
660		},
661	};
662
663	// don't remove the object right away because if we have the original key,
664	// we need to do a validation check that it matches the object's key in
665	// case of a hash collision
666	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}