Skip to main content

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 object;
10mod policy;
11mod status;
12mod worker;
13
14use std::{
15	hash::{BuildHasher, BuildHasherDefault, Hash, RandomState},
16	sync::{Arc, atomic::AtomicU64},
17	thread,
18};
19
20use crossbeam_channel::unbounded;
21use dashmap::{DashMap, mapref::entry::Entry};
22use kwik::{fmt, math::set::Multiset};
23use log::{error, info};
24use nohash_hasher::NoHashHasher;
25use typesize::TypeSize;
26
27pub use crate::{error::CacheError, policy::PaperPolicy};
28use crate::{
29	object::{Object, ObjectSize, overhead::OverheadManager},
30	status::{AtomicStatus, Status},
31	worker::{Worker, WorkerEvent, WorkerManager, WorkerSender},
32};
33
34pub type CacheSize = u64;
35pub type AtomicCacheSize = AtomicU64;
36
37pub type HashedKey = u64;
38pub type NoHasher = BuildHasherDefault<NoHashHasher<HashedKey>>;
39
40pub type ObjectMapRef<K, V> = Arc<DashMap<HashedKey, Object<K, V>, NoHasher>>;
41pub type StatusRef = Arc<AtomicStatus>;
42pub type OverheadManagerRef = Arc<OverheadManager>;
43
44pub struct PaperCache<K, V, S = RandomState> {
45	objects: ObjectMapRef<K, V>,
46	status:  StatusRef,
47
48	worker_manager:   Arc<WorkerSender>,
49	overhead_manager: OverheadManagerRef,
50
51	hasher: S,
52}
53
54impl<K, V, S> PaperCache<K, V, S>
55where
56	K: 'static + Eq + Hash + TypeSize,
57	V: 'static + TypeSize,
58	S: Default + Clone + BuildHasher,
59{
60	/// Creates an empty `PaperCache` with maximum size `max_size` and
61	/// eviction policy `policy`. If the maximum size is zero, a
62	/// [`CacheError`] will be returned.
63	///
64	/// # Examples
65	///
66	/// ```
67	/// use paper_cache::{PaperCache, PaperPolicy};
68	///
69	/// let cache = PaperCache::<u32, u32>::new(
70	///     1000,
71	///     &[PaperPolicy::Lfu],
72	///     PaperPolicy::Lfu,
73	/// );
74	///
75	/// assert!(cache.is_ok());
76	///
77	/// // Supplying a maximum size of zero will return a `CacheError`.
78	/// let cache = PaperCache::<u32, u32>::new(
79	///     0,
80	///     &[PaperPolicy::Lfu],
81	///     PaperPolicy::Lfu,
82	/// );
83	///
84	/// assert!(cache.is_err());
85	///
86	/// // Supplying duplicate policies will return a `CacheError`.
87	/// let cache = PaperCache::<u32, u32>::new(
88	///     1000,
89	///     &[PaperPolicy::Lfu, PaperPolicy::Lru, PaperPolicy::Lfu],
90	///     PaperPolicy::Lfu,
91	/// );
92	///
93	/// assert!(cache.is_err());
94	///
95	/// // Supplying a non-configured policy will return a `CacheError`.
96	/// let cache = PaperCache::<u32, u32>::new(
97	///     1000,
98	///     &[PaperPolicy::Lfu],
99	///     PaperPolicy::Lru,
100	/// );
101	///
102	/// assert!(cache.is_err());
103	/// ```
104	pub fn new(
105		max_size: CacheSize,
106		policies: &[PaperPolicy],
107		policy: PaperPolicy,
108	) -> Result<Self, CacheError> {
109		Self::with_hasher(max_size, policies, policy, Default::default())
110	}
111
112	/// Creates an empty `PaperCache` with the supplied hasher.
113	///
114	/// # Examples
115	///
116	/// ```
117	/// use std::hash::RandomState;
118	/// use paper_cache::{PaperCache, PaperPolicy};
119	///
120	/// let cache = PaperCache::<u32, u32>::with_hasher(
121	///     1000,
122	///     &[PaperPolicy::Lfu],
123	///     PaperPolicy::Lfu,
124	///     RandomState::default(),
125	/// );
126	///
127	/// assert!(cache.is_ok());
128	/// ```
129	pub fn with_hasher(
130		max_size: CacheSize,
131		policies: &[PaperPolicy],
132		policy: PaperPolicy,
133		hasher: S,
134	) -> Result<Self, CacheError> {
135		if max_size == 0 {
136			return Err(CacheError::ZeroCacheSize);
137		}
138
139		if policies.is_empty() {
140			return Err(CacheError::EmptyPolicies);
141		}
142
143		if policies.contains(&PaperPolicy::Auto) {
144			return Err(CacheError::ConfiguredAutoPolicy);
145		}
146
147		if policies.iter().is_multiset() {
148			return Err(CacheError::DuplicatePolicies);
149		}
150
151		if !policy.is_auto() && !policies.contains(&policy) {
152			return Err(CacheError::UnconfiguredPolicy);
153		}
154
155		let objects = Arc::new(DashMap::with_hasher(NoHasher::default()));
156		let status = Arc::new(AtomicStatus::new(max_size, policies, policy)?);
157		let overhead_manager = Arc::new(OverheadManager::new(&status));
158
159		let (worker_sender, worker_listener) = unbounded();
160
161		let mut worker_manager =
162			WorkerManager::new(worker_listener, &objects, &status, &overhead_manager)?;
163
164		thread::spawn(move || worker_manager.run());
165
166		let cache = PaperCache {
167			objects,
168			status,
169
170			worker_manager: Arc::new(worker_sender),
171			overhead_manager,
172
173			hasher,
174		};
175
176		Ok(cache)
177	}
178
179	/// Returns the current cache version.
180	///
181	/// # Examples
182	/// ```
183	/// use paper_cache::{PaperCache, PaperPolicy};
184	///
185	/// let mut cache = PaperCache::<u32, u32>::new(
186	///     1000,
187	///     &[PaperPolicy::Lfu],
188	///     PaperPolicy::Lfu
189	/// ).unwrap();
190	///
191	/// assert_eq!(cache.version(), env!("CARGO_PKG_VERSION"));
192	/// ```
193	#[must_use]
194	pub fn version(&self) -> String {
195		env!("CARGO_PKG_VERSION").to_owned()
196	}
197
198	/// Returns the current statistics.
199	///
200	/// # Examples
201	/// ```
202	/// use paper_cache::{PaperCache, PaperPolicy};
203	///
204	/// let mut cache = PaperCache::<u32, u32>::new(
205	///     1000,
206	///     &[PaperPolicy::Lfu],
207	///     PaperPolicy::Lfu,
208	/// ).unwrap();
209	///
210	/// cache.set(0, 0, None);
211	///
212	/// let status = cache.status().unwrap();
213	/// assert!(status.used_size() > 0);
214	/// ```
215	pub fn status(&self) -> Result<Status, CacheError> {
216		self.status.try_to_status()
217	}
218
219	/// Gets the value associated with the supplied key.
220	/// If the key was not found in the cache, returns a [`CacheError`].
221	///
222	/// # Examples
223	/// ```
224	/// use paper_cache::{PaperCache, PaperPolicy};
225	///
226	/// let mut cache = PaperCache::<u32, u32>::new(
227	///     1000,
228	///     &[PaperPolicy::Lfu],
229	///     PaperPolicy::Lfu,
230	/// ).unwrap();
231	///
232	/// cache.set(0, 0, None);
233	///
234	/// // Getting a key which exists in the cache will return the associated value.
235	/// assert!(cache.get(&0).is_ok());
236	/// // Getting a key which does not exist in the cache will return a CacheError.
237	/// assert!(cache.get(&1).is_err());
238	/// ```
239	pub fn get(&self, key: &K) -> Result<Arc<V>, CacheError> {
240		let hashed_key = self.hash_key(key);
241
242		let result = match self.objects.get(&hashed_key) {
243			Some(object) if object.key_matches(key) && !object.is_expired() => {
244				self.status.incr_hits();
245				Ok(object.data())
246			},
247
248			_ => {
249				self.status.incr_misses();
250				Err(CacheError::KeyNotFound)
251			},
252		};
253
254		self.broadcast(WorkerEvent::Get(hashed_key, result.is_ok()))?;
255
256		result
257	}
258
259	/// Sets the supplied key and value in the cache.
260	/// Returns a [`CacheError`] if the value size is zero or larger than
261	/// the cache's maximum size.
262	///
263	/// If the key already exists in the cache, the associated value is updated
264	/// to the supplied value.
265	///
266	/// # Examples
267	/// ```
268	/// use paper_cache::{PaperCache, PaperPolicy};
269	///
270	/// let mut cache = PaperCache::<u32, u32>::new(
271	///     1000,
272	///     &[PaperPolicy::Lfu],
273	///     PaperPolicy::Lfu,
274	/// ).unwrap();
275	///
276	/// assert!(cache.set(0, 0, None).is_ok());
277	/// ```
278	pub fn set(&self, key: K, value: V, ttl: Option<u32>) -> Result<(), CacheError> {
279		let hashed_key = self.hash_key(&key);
280
281		let object = Object::new(key, value, ttl);
282		let base_size = self.overhead_manager.base_size(&object);
283		let expiry = object.expiry();
284
285		if base_size == 0 {
286			return Err(CacheError::ZeroValueSize);
287		}
288
289		if self.status.exceeds_max_size(base_size) {
290			return Err(CacheError::ExceedingValueSize);
291		}
292
293		self.status.incr_sets();
294
295		let old_object_info = self
296			.objects
297			.insert(hashed_key, object)
298			.map(|old_object| {
299				let base_size = self.overhead_manager.base_size(&old_object);
300				let expiry = old_object.expiry();
301
302				(base_size, expiry)
303			});
304
305		let base_size_delta = if let Some((old_object_size, _)) = old_object_info {
306			base_size as i64 - old_object_size as i64
307		} else {
308			// the object is new, so increase the number of objects count
309			self.status.incr_num_objects();
310			base_size as i64
311		};
312
313		self.status.update_base_used_size(base_size_delta);
314		self.broadcast(WorkerEvent::Set(
315			hashed_key,
316			base_size,
317			expiry,
318			old_object_info,
319		))?;
320
321		Ok(())
322	}
323
324	/// Deletes the object associated with the supplied key in the cache.
325	/// Returns a [`CacheError`] if the key was not found in the cache.
326	///
327	/// # Examples
328	/// ```
329	/// use paper_cache::{PaperCache, PaperPolicy};
330	///
331	/// let mut cache = PaperCache::<u32, u32>::new(
332	///     1000,
333	///     &[PaperPolicy::Lfu],
334	///     PaperPolicy::Lfu,
335	/// ).unwrap();
336	///
337	/// cache.set(0, 0, None);
338	/// assert!(cache.del(&0).is_ok());
339	///
340	/// // Deleting a key which does not exist in the cache will return a CacheError.
341	/// assert!(cache.del(&1).is_err());
342	/// ```
343	pub fn del(&self, key: &K) -> Result<(), CacheError> {
344		let hashed_key = self.hash_key(key);
345
346		let (removed_hashed_key, object) = erase(
347			&self.objects,
348			&self.status,
349			&self.overhead_manager,
350			Some(EraseKey::Original(key, hashed_key)),
351		)?;
352
353		self.status.incr_dels();
354		self.broadcast(WorkerEvent::Del(removed_hashed_key, object.expiry()))?;
355
356		Ok(())
357	}
358
359	/// Checks if an object with the supplied key exists in the cache without
360	/// altering any of the cache's internal queues.
361	///
362	/// # Examples
363	/// ```
364	/// use paper_cache::{PaperCache, PaperPolicy};
365	///
366	/// let mut cache = PaperCache::<u32, u32>::new(
367	///     1000,
368	///     &[PaperPolicy::Lfu],
369	///     PaperPolicy::Lfu,
370	/// ).unwrap();
371	///
372	/// cache.set(0, 0, None);
373	///
374	/// assert!(cache.has(&0));
375	/// assert!(!cache.has(&1));
376	/// ```
377	pub fn has(&self, key: &K) -> bool {
378		let hashed_key = self.hash_key(key);
379
380		self.objects
381			.get(&hashed_key)
382			.is_some_and(|object| object.key_matches(key) && !object.is_expired())
383	}
384
385	/// Gets (peeks) the value associated with the supplied key without altering
386	/// any of the cache's internal queues.
387	/// If the key was not found in the cache, returns a [`CacheError`].
388	///
389	/// # Examples
390	/// ```
391	/// use paper_cache::{PaperCache, PaperPolicy};
392	///
393	/// let mut cache = PaperCache::<u32, u32>::new(
394	///     1000,
395	///     &[PaperPolicy::Lfu],
396	///     PaperPolicy::Lfu,
397	/// ).unwrap();
398	///
399	/// cache.set(0, 0, None);
400	/// cache.set(1, 0, None);
401	///
402	/// // Peeking a key which exists in the cache will return the associated value.
403	/// assert!(cache.peek(&0).is_ok());
404	/// // Peeking a key which does not exist in the cache will return a CacheError.
405	/// assert!(cache.peek(&2).is_err());
406	///
407	/// cache.set(2, 0, None);
408	///
409	/// // Peeking a key will not alter the eviction order of the objects.
410	/// assert!(cache.peek(&1).is_ok());
411	/// assert!(cache.peek(&2).is_ok());
412	/// ```
413	pub fn peek(&self, key: &K) -> Result<Arc<V>, CacheError> {
414		let hashed_key = self.hash_key(key);
415
416		match self.objects.get(&hashed_key) {
417			Some(object) if object.key_matches(key) && !object.is_expired() => Ok(object.data()),
418
419			_ => Err(CacheError::KeyNotFound),
420		}
421	}
422
423	/// Sets the TTL associated with the supplied key.
424	/// If the key was not found in the cache, returns a [`CacheError`].
425	///
426	/// # Examples
427	/// ```
428	/// use paper_cache::{PaperCache, PaperPolicy};
429	///
430	/// let mut cache = PaperCache::<u32, u32>::new(
431	///     1000,
432	///     &[PaperPolicy::Lfu],
433	///     PaperPolicy::Lfu,
434	/// ).unwrap();
435	///
436	/// cache.set(0, 0, None); // value will not expire
437	/// cache.ttl(&0, Some(5)); // value will expire in 5 seconds
438	/// ```
439	pub fn ttl(&self, key: &K, ttl: Option<u32>) -> Result<(), CacheError> {
440		let hashed_key = self.hash_key(key);
441
442		let mut object = match self.objects.get_mut(&hashed_key) {
443			Some(object) if object.key_matches(key) && !object.is_expired() => object,
444			_ => return Err(CacheError::KeyNotFound),
445		};
446
447		let old_expiry = object.expiry();
448		let old_base_size = self.overhead_manager.base_size(&object);
449
450		object.expires(ttl);
451
452		let new_expiry = object.expiry();
453		let new_base_size = self.overhead_manager.base_size(&object);
454
455		self.status
456			.update_base_used_size(new_base_size as i64 - old_base_size as i64);
457		self.broadcast(WorkerEvent::Ttl(hashed_key, old_expiry, new_expiry))?;
458
459		Ok(())
460	}
461
462	/// Gets the size of the value associated with the supplied key in bytes.
463	/// If the key was not found in the cache, returns a [`CacheError`].
464	///
465	/// # Examples
466	/// ```
467	/// use paper_cache::{PaperCache, PaperPolicy};
468	///
469	/// let mut cache = PaperCache::<u32, u32>::new(
470	///     1000,
471	///     &[PaperPolicy::Lfu],
472	///     PaperPolicy::Lfu,
473	/// ).unwrap();
474	///
475	/// cache.set(0, 0, None);
476	///
477	/// // Sizing a key which exists in the cache will return the size of the associated value.
478	/// assert!(cache.size(&0).is_ok());
479	/// // Sizing a key which does not exist in the cache will return a CacheError.
480	/// assert!(cache.size(&1).is_err());
481	/// ```
482	pub fn size(&self, key: &K) -> Result<ObjectSize, CacheError> {
483		let hashed_key = self.hash_key(key);
484
485		match self.objects.get(&hashed_key) {
486			Some(object) if object.key_matches(key) && !object.is_expired() => {
487				Ok(self.overhead_manager.total_size(&object))
488			},
489
490			_ => Err(CacheError::KeyNotFound),
491		}
492	}
493
494	/// Deletes all objects in the cache and sets the cache's used size to zero.
495	/// Returns a [`CacheError`] if the objects could not be wiped.
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.wipe();
508	/// ```
509	pub fn wipe(&self) -> Result<(), CacheError> {
510		info!("Wiping cache");
511
512		self.objects.clear();
513		self.status.clear();
514
515		self.broadcast(WorkerEvent::Wipe)?;
516
517		Ok(())
518	}
519
520	/// Resizes the cache to the supplied maximum size.
521	/// If the supplied size is zero, returns a [`CacheError`].
522	///
523	/// # Examples
524	/// ```
525	/// use paper_cache::{PaperCache, PaperPolicy};
526	///
527	/// let mut cache = PaperCache::<u32, u32>::new(
528	///     1000,
529	///     &[PaperPolicy::Lfu],
530	///     PaperPolicy::Lfu,
531	/// ).unwrap();
532	///
533	/// assert!(cache.resize(1).is_ok());
534	///
535	/// // Resizing to a size of zero will return a CacheError.
536	/// assert!(cache.resize(0).is_err());
537	/// ```
538	pub fn resize(&self, max_size: CacheSize) -> Result<(), CacheError> {
539		if max_size == 0 {
540			return Err(CacheError::ZeroCacheSize);
541		}
542
543		let current_max_size = self.status.max_size();
544
545		if max_size == current_max_size {
546			return Ok(());
547		}
548
549		info!(
550			"Resizing cache from {} to {}",
551			fmt::memory(current_max_size, Some(2)),
552			fmt::memory(max_size, Some(2)),
553		);
554
555		self.status.set_max_size(max_size);
556		self.broadcast(WorkerEvent::Resize(max_size))?;
557
558		Ok(())
559	}
560
561	/// Sets the eviction policy of the cache to the supplied policy.
562	///
563	/// # Examples
564	/// ```
565	/// use paper_cache::{PaperCache, PaperPolicy};
566	///
567	/// let mut cache = PaperCache::<u32, u32>::new(
568	///     1000,
569	///     &[PaperPolicy::Lfu],
570	///     PaperPolicy::Lfu,
571	/// ).unwrap();
572	///
573	/// assert!(cache.policy(PaperPolicy::Lfu).is_ok());
574	/// assert!(cache.policy(PaperPolicy::Lru).is_err());
575	/// ```
576	pub fn policy(&self, policy: PaperPolicy) -> Result<(), CacheError> {
577		if !policy.is_auto() && !self.status.policies().contains(&policy) {
578			return Err(CacheError::UnconfiguredPolicy);
579		}
580
581		self.status.set_policy(policy)?;
582		self.broadcast(WorkerEvent::Policy(policy))?;
583
584		Ok(())
585	}
586
587	fn broadcast(&self, event: WorkerEvent) -> Result<(), CacheError> {
588		if let Err(err) = self.worker_manager.try_send(event) {
589			error!("Could not communicate with workers: {err:?}");
590			return Err(CacheError::Internal);
591		}
592
593		Ok(())
594	}
595
596	fn hash_key(&self, key: &K) -> HashedKey {
597		self.hasher.hash_one(key)
598	}
599}
600
601pub enum EraseKey<'a, K> {
602	Original(&'a K, HashedKey),
603	Hashed(HashedKey),
604}
605
606pub fn erase<K, V>(
607	objects: &ObjectMapRef<K, V>,
608	status: &StatusRef,
609	overhead_manager: &OverheadManagerRef,
610	maybe_key: Option<EraseKey<K>>,
611) -> Result<(HashedKey, Object<K, V>), CacheError>
612where
613	K: Eq + TypeSize,
614	V: TypeSize,
615{
616	let hashed_key = match maybe_key {
617		Some(EraseKey::Original(_, hashed_key)) => hashed_key,
618		Some(EraseKey::Hashed(hashed_key)) => hashed_key,
619
620		None => {
621			// the policy has run out of keys to evict (either it's a mini stack or
622			// something went wrong during policy reconstruction) so we fall back
623			// to evicting a random object
624
625			let Some(object) = objects.iter().next() else {
626				error!("Object store is empty with non-zero used size");
627				return Err(CacheError::Internal);
628			};
629
630			object.key().to_owned()
631		},
632	};
633
634	// don't remove the object right away because if we have the original key,
635	// we need to do a validation check that it matches the object's key in
636	// case of a hash collision
637	let Entry::Occupied(entry) = objects.entry(hashed_key) else {
638		return Err(CacheError::KeyNotFound);
639	};
640
641	if let Some(EraseKey::Original(key, _)) = maybe_key
642		&& !entry.get().key_matches(key)
643	{
644		return Err(CacheError::KeyNotFound);
645	};
646
647	let object = entry.remove();
648	let base_size = overhead_manager.base_size(&object) as i64;
649
650	status.update_base_used_size(-base_size);
651	status.decr_num_objects();
652
653	match !object.is_expired() {
654		true => Ok((hashed_key, object)),
655		false => Err(CacheError::KeyNotFound),
656	}
657}
658
659unsafe impl<K, V, S> Send for PaperCache<K, V, S> {}
660
661#[cfg(test)]
662mod tests {
663	use crate::{CacheError, PaperCache, PaperPolicy};
664
665	const TEST_CACHE_MAX_SIZE: u64 = 1000;
666
667	#[test]
668	fn it_returns_correct_version() {
669		let cache = init_test_cache();
670		assert_eq!(cache.version(), env!("CARGO_PKG_VERSION"));
671	}
672
673	#[test]
674	fn it_returns_status() {
675		let cache = init_test_cache();
676		let status = cache.status().unwrap();
677
678		assert_eq!(status.max_size(), TEST_CACHE_MAX_SIZE);
679	}
680
681	#[test]
682	fn it_gets_an_existing_object() {
683		let cache = init_test_cache();
684
685		assert!(cache.set(0, 1, None).is_ok());
686		assert_eq!(cache.get(&0).as_deref(), Ok(&1));
687	}
688
689	#[test]
690	fn it_does_not_get_a_non_existing_object() {
691		let cache = init_test_cache();
692
693		assert!(cache.set(0, 1, None).is_ok());
694		assert_eq!(cache.get(&1), Err(CacheError::KeyNotFound));
695	}
696
697	#[test]
698	fn it_calculates_miss_ratio_correctly() {
699		let cache = init_test_cache();
700
701		assert!(cache.set(0, 1, None).is_ok());
702		assert!(cache.get(&0).is_ok());
703		assert!(cache.get(&0).is_ok());
704		assert!(cache.get(&0).is_ok());
705		assert!(cache.get(&1).is_err());
706
707		let status = cache.status().unwrap();
708		assert_eq!(status.miss_ratio(), 0.25);
709	}
710
711	#[test]
712	fn it_sets_with_no_ttl() {
713		let cache = init_test_cache();
714
715		assert!(cache.set(0, 1, None).is_ok());
716		assert!(cache.get(&0).is_ok());
717	}
718
719	#[test]
720	fn it_sets_with_ttl() {
721		use std::{thread, time::Duration};
722
723		let cache = init_test_cache();
724		assert!(cache.set(0, 1, Some(1)).is_ok());
725
726		assert!(cache.get(&0).is_ok());
727		thread::sleep(Duration::from_secs(2));
728		assert!(cache.get(&0).is_err());
729	}
730
731	#[test]
732	fn it_dels_an_existing_object() {
733		let cache = init_test_cache();
734		assert!(cache.set(0, 1, Some(1)).is_ok());
735
736		assert!(cache.get(&0).is_ok());
737		assert!(cache.del(&0).is_ok());
738		assert!(cache.get(&0).is_err());
739	}
740
741	#[test]
742	fn it_does_not_del_a_non_existing_object() {
743		let cache = init_test_cache();
744		assert_eq!(cache.del(&0), Err(CacheError::KeyNotFound));
745	}
746
747	#[test]
748	fn it_does_not_allow_empty_policies() {
749		let try_cache = PaperCache::<u32, u32>::new(TEST_CACHE_MAX_SIZE, &[], PaperPolicy::Lfu);
750
751		assert!(try_cache.is_err_and(|err| err == CacheError::EmptyPolicies));
752
753		let try_cache = PaperCache::<u32, u32>::new(TEST_CACHE_MAX_SIZE, &[], PaperPolicy::Auto);
754
755		assert!(try_cache.is_err_and(|err| err == CacheError::EmptyPolicies));
756	}
757
758	#[test]
759	fn it_does_not_allow_auto_policy_in_configured_policies() {
760		let try_cache = PaperCache::<u32, u32>::new(
761			TEST_CACHE_MAX_SIZE,
762			&[PaperPolicy::Auto],
763			PaperPolicy::Auto,
764		);
765
766		assert!(try_cache.is_err_and(|err| err == CacheError::ConfiguredAutoPolicy));
767
768		let try_cache = PaperCache::<u32, u32>::new(
769			TEST_CACHE_MAX_SIZE,
770			&[
771				PaperPolicy::Auto,
772				PaperPolicy::Lru,
773			],
774			PaperPolicy::Auto,
775		);
776
777		assert!(try_cache.is_err_and(|err| err == CacheError::ConfiguredAutoPolicy));
778	}
779
780	#[test]
781	fn it_does_not_allow_duplicate_policies() {
782		let try_cache = PaperCache::<u32, u32>::new(
783			TEST_CACHE_MAX_SIZE,
784			&[
785				PaperPolicy::Lfu,
786				PaperPolicy::Lru,
787				PaperPolicy::Lfu,
788			],
789			PaperPolicy::Lfu,
790		);
791
792		assert!(try_cache.is_err_and(|err| err == CacheError::DuplicatePolicies));
793
794		let try_cache = PaperCache::<u32, u32>::new(
795			TEST_CACHE_MAX_SIZE,
796			&[
797				PaperPolicy::Lfu,
798				PaperPolicy::Lru,
799			],
800			PaperPolicy::Lfu,
801		);
802
803		assert!(try_cache.is_ok());
804	}
805
806	#[test]
807	fn it_has_an_existing_object() {
808		let cache = init_test_cache();
809
810		assert!(cache.set(0, 1, Some(1)).is_ok());
811		assert!(cache.has(&0));
812	}
813
814	#[test]
815	fn it_does_not_have_a_non_existing_object() {
816		let cache = init_test_cache();
817		assert!(!cache.has(&1));
818	}
819
820	#[test]
821	fn it_peeks_an_existing_object() {
822		let cache = init_test_cache();
823
824		assert!(cache.set(0, 1, None).is_ok());
825		assert_eq!(cache.peek(&0).as_deref(), Ok(&1));
826	}
827
828	#[test]
829	fn it_does_not_peek_a_non_existing_object() {
830		let cache = init_test_cache();
831
832		assert!(cache.set(0, 1, None).is_ok());
833		assert_eq!(cache.peek(&1), Err(CacheError::KeyNotFound));
834	}
835
836	#[test]
837	fn it_sets_an_existing_objects_ttl() {
838		use std::{thread, time::Duration};
839
840		let cache = init_test_cache();
841
842		assert!(cache.set(0, 1, None).is_ok());
843		assert!(cache.get(&0).is_ok());
844
845		assert!(cache.ttl(&0, Some(1)).is_ok());
846
847		thread::sleep(Duration::from_secs(2));
848		assert_eq!(cache.get(&0), Err(CacheError::KeyNotFound));
849	}
850
851	#[test]
852	fn it_does_not_set_a_non_existing_objects_ttl() {
853		let cache = init_test_cache();
854		assert_eq!(cache.ttl(&0, Some(1)), Err(CacheError::KeyNotFound));
855	}
856
857	#[test]
858	fn it_resets_an_objects_ttl() {
859		use std::{thread, time::Duration};
860
861		let cache = init_test_cache();
862
863		assert!(cache.set(0, 1, Some(1)).is_ok());
864		assert!(cache.get(&0).is_ok());
865
866		assert!(cache.ttl(&0, Some(5)).is_ok());
867
868		thread::sleep(Duration::from_secs(2));
869		assert!(cache.get(&0).is_ok());
870	}
871
872	#[test]
873	fn it_gets_an_objects_size() {
874		use std::mem;
875
876		use crate::object::{ExpireTime, overhead::get_policy_overhead};
877
878		let cache = init_test_cache();
879
880		let expected =
881			4 + 4 + mem::size_of::<ExpireTime>() as u32 + get_policy_overhead(&PaperPolicy::Lfu);
882
883		assert!(cache.set(0, 1, None).is_ok());
884		assert_eq!(cache.size(&0), Ok(expected));
885	}
886
887	#[test]
888	fn it_gets_an_expiring_objects_size() {
889		use std::mem;
890
891		use crate::object::{
892			ExpireTime,
893			overhead::{get_policy_overhead, get_ttl_overhead},
894		};
895
896		let cache = init_test_cache();
897
898		let expected = 4
899			+ 4 + mem::size_of::<ExpireTime>() as u32
900			+ get_policy_overhead(&PaperPolicy::Lfu)
901			+ get_ttl_overhead();
902
903		assert!(cache.set(0, 1, Some(1)).is_ok());
904		assert_eq!(cache.size(&0), Ok(expected));
905	}
906
907	#[test]
908	fn it_gets_an_objects_size_after_policy_switch() {
909		use std::mem;
910
911		use crate::object::{ExpireTime, overhead::get_policy_overhead};
912
913		let cache = PaperCache::<u32, u32>::new(
914			TEST_CACHE_MAX_SIZE,
915			&[
916				PaperPolicy::Lru,
917				PaperPolicy::Lfu,
918			],
919			PaperPolicy::Lfu,
920		)
921		.expect("Could not initialize test cache");
922
923		let base_expected = 4 + 4 + mem::size_of::<ExpireTime>() as u32;
924		let lfu_expected = base_expected + get_policy_overhead(&PaperPolicy::Lfu);
925		let lru_expected = base_expected + get_policy_overhead(&PaperPolicy::Lru);
926
927		assert!(cache.set(0, 1, None).is_ok());
928		assert_eq!(cache.size(&0), Ok(lfu_expected));
929
930		assert!(cache.policy(PaperPolicy::Lru).is_ok());
931		assert_eq!(cache.size(&0), Ok(lru_expected));
932	}
933
934	#[test]
935	fn it_gets_an_objects_size_after_set_ttl() {
936		use std::mem;
937
938		use crate::object::{
939			ExpireTime,
940			overhead::{get_policy_overhead, get_ttl_overhead},
941		};
942
943		let cache = init_test_cache();
944
945		let pre_expected =
946			4 + 4 + mem::size_of::<ExpireTime>() as u32 + get_policy_overhead(&PaperPolicy::Lfu);
947
948		let post_expected = pre_expected + get_ttl_overhead();
949
950		assert!(cache.set(0, 1, None).is_ok());
951		assert_eq!(cache.size(&0), Ok(pre_expected));
952
953		assert!(cache.ttl(&0, Some(1)).is_ok());
954		assert_eq!(cache.size(&0), Ok(post_expected));
955	}
956
957	#[test]
958	fn it_gets_an_objects_size_after_unset_ttl() {
959		use std::mem;
960
961		use crate::object::{
962			ExpireTime,
963			overhead::{get_policy_overhead, get_ttl_overhead},
964		};
965
966		let cache = init_test_cache();
967
968		let pre_expected = 4
969			+ 4 + mem::size_of::<ExpireTime>() as u32
970			+ get_policy_overhead(&PaperPolicy::Lfu)
971			+ get_ttl_overhead();
972
973		let post_expected = pre_expected - get_ttl_overhead();
974
975		assert!(cache.set(0, 1, Some(1)).is_ok());
976		assert_eq!(cache.size(&0), Ok(pre_expected));
977
978		assert!(cache.ttl(&0, None).is_ok());
979		assert_eq!(cache.size(&0), Ok(post_expected));
980	}
981
982	#[test]
983	fn status_shows_correct_used_size() {
984		use std::mem;
985
986		use crate::object::{
987			ExpireTime,
988			overhead::{get_policy_overhead, get_ttl_overhead},
989		};
990
991		let cache = init_test_cache();
992
993		let expected = (4 + 4) * 2
994			+ mem::size_of::<ExpireTime>() as u32 * 2
995			+ get_policy_overhead(&PaperPolicy::Lfu) * 2
996			+ get_ttl_overhead();
997
998		assert!(cache.set(0, 1, None).is_ok());
999		assert!(cache.set(1, 1, Some(1)).is_ok());
1000
1001		let status = cache.status().unwrap();
1002		assert_eq!(status.used_size(), expected as u64);
1003	}
1004
1005	#[test]
1006	fn status_shows_correct_used_size_after_policy_switch() {
1007		use std::mem;
1008
1009		use crate::object::{ExpireTime, overhead::get_policy_overhead};
1010
1011		let cache = PaperCache::<u32, u32>::new(
1012			TEST_CACHE_MAX_SIZE,
1013			&[
1014				PaperPolicy::Lru,
1015				PaperPolicy::Lfu,
1016			],
1017			PaperPolicy::Lfu,
1018		)
1019		.expect("Could not initialize test cache");
1020
1021		let base_expected = 4 + 4 + mem::size_of::<ExpireTime>() as u32;
1022		let lfu_expected = base_expected + get_policy_overhead(&PaperPolicy::Lfu);
1023		let lru_expected = base_expected + get_policy_overhead(&PaperPolicy::Lru);
1024
1025		assert!(cache.set(0, 1, None).is_ok());
1026		let status = cache.status().unwrap();
1027		assert_eq!(status.used_size(), lfu_expected as u64);
1028
1029		assert!(cache.policy(PaperPolicy::Lru).is_ok());
1030		let status = cache.status().unwrap();
1031		assert_eq!(status.used_size(), lru_expected as u64);
1032	}
1033
1034	#[test]
1035	fn status_shows_correct_used_size_after_set_ttl() {
1036		use std::mem;
1037
1038		use crate::object::{
1039			ExpireTime,
1040			overhead::{get_policy_overhead, get_ttl_overhead},
1041		};
1042
1043		let cache = init_test_cache();
1044
1045		let pre_expected =
1046			4 + 4 + mem::size_of::<ExpireTime>() as u32 + get_policy_overhead(&PaperPolicy::Lfu);
1047
1048		let post_expected = pre_expected + get_ttl_overhead();
1049
1050		assert!(cache.set(0, 1, None).is_ok());
1051		let status = cache.status().unwrap();
1052		assert_eq!(status.used_size(), pre_expected as u64);
1053
1054		assert!(cache.ttl(&0, Some(1)).is_ok());
1055		let status = cache.status().unwrap();
1056		assert_eq!(status.used_size(), post_expected as u64);
1057	}
1058
1059	#[test]
1060	fn status_shows_correct_used_size_after_unset_ttl() {
1061		use std::mem;
1062
1063		use crate::object::{
1064			ExpireTime,
1065			overhead::{get_policy_overhead, get_ttl_overhead},
1066		};
1067
1068		let cache = init_test_cache();
1069
1070		let pre_expected = 4
1071			+ 4 + mem::size_of::<ExpireTime>() as u32
1072			+ get_policy_overhead(&PaperPolicy::Lfu)
1073			+ get_ttl_overhead();
1074
1075		let post_expected = pre_expected - get_ttl_overhead();
1076
1077		assert!(cache.set(0, 1, Some(1)).is_ok());
1078		let status = cache.status().unwrap();
1079		assert_eq!(status.used_size(), pre_expected as u64);
1080
1081		assert!(cache.ttl(&0, None).is_ok());
1082		let status = cache.status().unwrap();
1083		assert_eq!(status.used_size(), post_expected as u64);
1084	}
1085
1086	fn init_test_cache() -> PaperCache<u32, u32> {
1087		PaperCache::<u32, u32>::new(TEST_CACHE_MAX_SIZE, &[PaperPolicy::Lfu], PaperPolicy::Lfu)
1088			.expect("Could not initialize test cache")
1089	}
1090}