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 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	/// 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 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	/// 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	/// let status = cache.status().unwrap();
251	/// assert!(status.used_size() > 0);
252	/// ```
253	pub fn status(&self) -> Result<Status, CacheError> {
254		self.status.try_to_status()
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.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	/// 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.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			// the object is new, so increase the number of objects count
346			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	/// 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.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	/// 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.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	/// 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.status.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.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	/// 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.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			// the policy has run out of keys to evict (either it's a mini stack or
653			// something went wrong during policy reconstruction) so we fall back
654			// to evicting a random object
655
656			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	// don't remove the object right away because if we have the original key,
666	// we need to do a validation check that it matches the object's key in
667	// case of a hash collision
668	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}