moka/policy.rs
1use std::{
2 fmt,
3 ops::Deref,
4 sync::Arc,
5 time::{Duration, Instant},
6};
7
8#[derive(Clone, Debug)]
9/// The policy of a cache.
10pub struct Policy {
11 max_capacity: Option<u64>,
12 num_segments: usize,
13 time_to_live: Option<Duration>,
14 time_to_idle: Option<Duration>,
15}
16
17impl Policy {
18 pub(crate) fn new(
19 max_capacity: Option<u64>,
20 num_segments: usize,
21 time_to_live: Option<Duration>,
22 time_to_idle: Option<Duration>,
23 ) -> Self {
24 Self {
25 max_capacity,
26 num_segments,
27 time_to_live,
28 time_to_idle,
29 }
30 }
31
32 /// Returns the `max_capacity` of the cache.
33 pub fn max_capacity(&self) -> Option<u64> {
34 self.max_capacity
35 }
36
37 #[cfg(feature = "sync")]
38 pub(crate) fn set_max_capacity(&mut self, capacity: Option<u64>) {
39 self.max_capacity = capacity;
40 }
41
42 /// Returns the number of internal segments of the cache.
43 pub fn num_segments(&self) -> usize {
44 self.num_segments
45 }
46
47 #[cfg(feature = "sync")]
48 pub(crate) fn set_num_segments(&mut self, num: usize) {
49 self.num_segments = num;
50 }
51
52 /// Returns the `time_to_live` of the cache.
53 pub fn time_to_live(&self) -> Option<Duration> {
54 self.time_to_live
55 }
56
57 /// Returns the `time_to_idle` of the cache.
58 pub fn time_to_idle(&self) -> Option<Duration> {
59 self.time_to_idle
60 }
61}
62
63/// The eviction (and admission) policy of a cache.
64///
65/// When the cache is full, the eviction/admission policy is used to determine which
66/// items should be admitted to the cache and which cached items should be evicted.
67/// The choice of a policy will directly affect the performance (hit rate) of the
68/// cache.
69///
70/// The following policies are available:
71///
72/// - **TinyLFU** (default):
73/// - Suitable for most workloads.
74/// - TinyLFU combines the LRU eviction policy and an admission policy based on the
75/// historical popularity of keys.
76/// - Note that it tracks not only the keys currently in the cache, but all hit and
77/// missed keys. The data structure used to _estimate_ the popularity of keys is
78/// a modified Count-Min Sketch, which has a very low memory footprint (thus the
79/// name "tiny").
80/// - **LRU**:
81/// - Suitable for some workloads with strong recency bias, such as streaming data
82/// processing.
83///
84/// LFU stands for Least Frequently Used. LRU stands for Least Recently Used.
85///
86/// Use associate function [`EvictionPolicy::tiny_lfu`](#method.tiny_lfu) or
87/// [`EvictionPolicy::lru`](#method.lru) to obtain an instance of `EvictionPolicy`.
88#[derive(Clone, Default)]
89pub struct EvictionPolicy {
90 pub(crate) config: EvictionPolicyConfig,
91}
92
93impl EvictionPolicy {
94 /// Returns the TinyLFU policy, which is suitable for most workloads.
95 ///
96 /// TinyLFU is a combination of the LRU eviction policy and the admission policy
97 /// based on the historical popularity of keys.
98 ///
99 /// Note that it tracks not only the keys currently in the cache, but all hit and
100 /// missed keys. The data structure used to _estimate_ the popularity of keys is
101 /// a modified Count-Min Sketch, which has a very low memory footprint (thus the
102 /// name "tiny").
103 pub fn tiny_lfu() -> Self {
104 Self {
105 config: EvictionPolicyConfig::TinyLfu,
106 }
107 }
108
109 /// Returns the LRU policy.
110 ///
111 /// Suitable for some workloads with strong recency bias, such as streaming data
112 /// processing.
113 pub fn lru() -> Self {
114 Self {
115 config: EvictionPolicyConfig::Lru,
116 }
117 }
118}
119
120impl fmt::Debug for EvictionPolicy {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 match self.config {
123 EvictionPolicyConfig::TinyLfu => write!(f, "EvictionPolicy::TinyLfu"),
124 EvictionPolicyConfig::Lru => write!(f, "EvictionPolicy::Lru"),
125 }
126 }
127}
128
129#[derive(Clone, Debug, Default, PartialEq, Eq)]
130pub(crate) enum EvictionPolicyConfig {
131 #[default]
132 TinyLfu,
133 Lru,
134}
135
136/// Calculates when cache entries expire. A single expiration time is retained on
137/// each entry so that the lifetime of an entry may be extended or reduced by
138/// subsequent evaluations.
139///
140/// `Expiry` trait provides three methods. They specify the expiration time of an
141/// entry by returning a `Some(duration)` until the entry expires:
142///
143/// - [`expire_after_create`](#method.expire_after_create) — Returns the
144/// duration (or none) after the entry's creation.
145/// - [`expire_after_read`](#method.expire_after_read) — Returns the duration
146/// (or none) after its last read.
147/// - [`expire_after_update`](#method.expire_after_update) — Returns the
148/// duration (or none) after its last update.
149///
150/// The default implementations are provided that return `None` (no expiration) or
151/// `current_duration: Option<Instant>` (not modify the current expiration time).
152/// Override some of them as you need.
153///
154pub trait Expiry<K, V> {
155 /// Specifies that the entry should be automatically removed from the cache once
156 /// the duration has elapsed after the entry's creation. This method is called
157 /// for cache write methods such as `insert` and `get_with` but only when the key
158 /// was not present in the cache.
159 ///
160 /// # Parameters
161 ///
162 /// - `key` — A reference to the key of the entry.
163 /// - `value` — A reference to the value of the entry.
164 /// - `created_at` — The time when this entry was inserted.
165 ///
166 /// # Return value
167 ///
168 /// The returned `Option<Duration>` is used to set the expiration time of the
169 /// entry.
170 ///
171 /// - Returning `Some(duration)` — The expiration time is set to
172 /// `created_at + duration`.
173 /// - Returning `None` — The expiration time is cleared (no expiration).
174 /// - This is the value that the default implementation returns.
175 ///
176 /// # Notes on `time_to_live` and `time_to_idle` policies
177 ///
178 /// When the cache is configured with `time_to_live` and/or `time_to_idle`
179 /// policies, the entry will be evicted after the earliest of the expiration time
180 /// returned by this expiry, the `time_to_live` and `time_to_idle` policies.
181 #[allow(unused_variables)]
182 fn expire_after_create(&self, key: &K, value: &V, created_at: Instant) -> Option<Duration> {
183 None
184 }
185
186 /// Specifies that the entry should be automatically removed from the cache once
187 /// the duration has elapsed after its last read. This method is called for cache
188 /// read methods such as `get` and `get_with` but only when the key is present in
189 /// the cache.
190 ///
191 /// # Parameters
192 ///
193 /// - `key` — A reference to the key of the entry.
194 /// - `value` — A reference to the value of the entry.
195 /// - `read_at` — The time when this entry was read.
196 /// - `duration_until_expiry` — The remaining duration until the entry
197 /// expires. (Calculated by `expiration_time - read_at`)
198 /// - `last_modified_at` — The time when this entry was created or updated.
199 ///
200 /// # Return value
201 ///
202 /// The returned `Option<Duration>` is used to set the expiration time of the
203 /// entry.
204 ///
205 /// - Returning `Some(duration)` — The expiration time is set to
206 /// `read_at + duration`.
207 /// - Returning `None` — The expiration time is cleared (no expiration).
208 /// - Returning `duration_until_expiry` will not modify the expiration time.
209 /// - This is the value that the default implementation returns.
210 ///
211 /// # Notes on `time_to_live` and `time_to_idle` policies
212 ///
213 /// When the cache is configured with `time_to_live` and/or `time_to_idle`
214 /// policies, then:
215 ///
216 /// - The entry will be evicted after the earliest of the expiration time
217 /// returned by this expiry, the `time_to_live` and `time_to_idle` policies.
218 /// - The `duration_until_expiry` takes in account the `time_to_live` and
219 /// `time_to_idle` policies.
220 #[allow(unused_variables)]
221 fn expire_after_read(
222 &self,
223 key: &K,
224 value: &V,
225 read_at: Instant,
226 duration_until_expiry: Option<Duration>,
227 last_modified_at: Instant,
228 ) -> Option<Duration> {
229 duration_until_expiry
230 }
231
232 /// Specifies that the entry should be automatically removed from the cache once
233 /// the duration has elapsed after the replacement of its value. This method is
234 /// called for cache write methods such as `insert` but only when the key is
235 /// already present in the cache.
236 ///
237 /// # Parameters
238 ///
239 /// - `key` — A reference to the key of the entry.
240 /// - `value` — A reference to the value of the entry.
241 /// - `updated_at` — The time when this entry was updated.
242 /// - `duration_until_expiry` — The remaining duration until the entry
243 /// expires. (Calculated by `expiration_time - updated_at`)
244 ///
245 /// # Return value
246 ///
247 /// The returned `Option<Duration>` is used to set the expiration time of the
248 /// entry.
249 ///
250 /// - Returning `Some(duration)` — The expiration time is set to
251 /// `updated_at + duration`.
252 /// - Returning `None` — The expiration time is cleared (no expiration).
253 /// - Returning `duration_until_expiry` will not modify the expiration time.
254 /// - This is the value that the default implementation returns.
255 ///
256 /// # Notes on `time_to_live` and `time_to_idle` policies
257 ///
258 /// When the cache is configured with `time_to_live` and/or `time_to_idle`
259 /// policies, then:
260 ///
261 /// - The entry will be evicted after the earliest of the expiration time
262 /// returned by this expiry, the `time_to_live` and `time_to_idle` policies.
263 /// - The `duration_until_expiry` takes in account the `time_to_live` and
264 /// `time_to_idle` policies.
265 #[allow(unused_variables)]
266 fn expire_after_update(
267 &self,
268 key: &K,
269 value: &V,
270 updated_at: Instant,
271 duration_until_expiry: Option<Duration>,
272 ) -> Option<Duration> {
273 duration_until_expiry
274 }
275}
276
277impl<K, V, T> Expiry<K, V> for T
278where
279 T: Deref<Target = dyn Expiry<K, V> + Send + Sync>,
280{
281 fn expire_after_create(&self, key: &K, value: &V, created_at: Instant) -> Option<Duration> {
282 self.deref().expire_after_create(key, value, created_at)
283 }
284
285 fn expire_after_read(
286 &self,
287 key: &K,
288 value: &V,
289 read_at: Instant,
290 duration_until_expiry: Option<Duration>,
291 last_modified_at: Instant,
292 ) -> Option<Duration> {
293 self.deref()
294 .expire_after_read(key, value, read_at, duration_until_expiry, last_modified_at)
295 }
296
297 fn expire_after_update(
298 &self,
299 key: &K,
300 value: &V,
301 updated_at: Instant,
302 duration_until_expiry: Option<Duration>,
303 ) -> Option<Duration> {
304 self.deref()
305 .expire_after_update(key, value, updated_at, duration_until_expiry)
306 }
307}
308
309pub(crate) struct ExpirationPolicy<K, V> {
310 time_to_live: Option<Duration>,
311 time_to_idle: Option<Duration>,
312 expiry: Option<Arc<dyn Expiry<K, V> + Send + Sync + 'static>>,
313}
314
315impl<K, V> Default for ExpirationPolicy<K, V> {
316 fn default() -> Self {
317 Self {
318 time_to_live: None,
319 time_to_idle: None,
320 expiry: None,
321 }
322 }
323}
324
325impl<K, V> Clone for ExpirationPolicy<K, V> {
326 fn clone(&self) -> Self {
327 Self {
328 time_to_live: self.time_to_live,
329 time_to_idle: self.time_to_idle,
330 expiry: self.expiry.clone(),
331 }
332 }
333}
334
335impl<K, V> ExpirationPolicy<K, V> {
336 #[cfg(test)]
337 pub(crate) fn new(
338 time_to_live: Option<Duration>,
339 time_to_idle: Option<Duration>,
340 expiry: Option<Arc<dyn Expiry<K, V> + Send + Sync + 'static>>,
341 ) -> Self {
342 Self {
343 time_to_live,
344 time_to_idle,
345 expiry,
346 }
347 }
348
349 /// Returns the `time_to_live` of the cache.
350 pub(crate) fn time_to_live(&self) -> Option<Duration> {
351 self.time_to_live
352 }
353
354 pub(crate) fn set_time_to_live(&mut self, duration: Duration) {
355 self.time_to_live = Some(duration);
356 }
357
358 /// Returns the `time_to_idle` of the cache.
359 pub(crate) fn time_to_idle(&self) -> Option<Duration> {
360 self.time_to_idle
361 }
362
363 pub(crate) fn set_time_to_idle(&mut self, duration: Duration) {
364 self.time_to_idle = Some(duration);
365 }
366
367 pub(crate) fn expiry(&self) -> Option<Arc<dyn Expiry<K, V> + Send + Sync + 'static>> {
368 self.expiry.clone()
369 }
370
371 pub(crate) fn set_expiry(&mut self, expiry: Arc<dyn Expiry<K, V> + Send + Sync + 'static>) {
372 self.expiry = Some(expiry);
373 }
374}
375
376#[cfg(test)]
377pub(crate) mod test_utils {
378 use std::sync::atomic::{AtomicU8, Ordering};
379
380 #[derive(Default)]
381 pub(crate) struct ExpiryCallCounters {
382 expected_creations: AtomicU8,
383 expected_reads: AtomicU8,
384 expected_updates: AtomicU8,
385 actual_creations: AtomicU8,
386 actual_reads: AtomicU8,
387 actual_updates: AtomicU8,
388 }
389
390 impl ExpiryCallCounters {
391 pub(crate) fn incl_expected_creations(&self) {
392 self.expected_creations.fetch_add(1, Ordering::Relaxed);
393 }
394
395 pub(crate) fn incl_expected_reads(&self) {
396 self.expected_reads.fetch_add(1, Ordering::Relaxed);
397 }
398
399 pub(crate) fn incl_expected_updates(&self) {
400 self.expected_updates.fetch_add(1, Ordering::Relaxed);
401 }
402
403 pub(crate) fn incl_actual_creations(&self) {
404 self.actual_creations.fetch_add(1, Ordering::Relaxed);
405 }
406
407 pub(crate) fn incl_actual_reads(&self) {
408 self.actual_reads.fetch_add(1, Ordering::Relaxed);
409 }
410
411 pub(crate) fn incl_actual_updates(&self) {
412 self.actual_updates.fetch_add(1, Ordering::Relaxed);
413 }
414
415 pub(crate) fn verify(&self) {
416 assert_eq!(
417 self.expected_creations.load(Ordering::Relaxed),
418 self.actual_creations.load(Ordering::Relaxed),
419 "expected_creations != actual_creations"
420 );
421 assert_eq!(
422 self.expected_reads.load(Ordering::Relaxed),
423 self.actual_reads.load(Ordering::Relaxed),
424 "expected_reads != actual_reads"
425 );
426 assert_eq!(
427 self.expected_updates.load(Ordering::Relaxed),
428 self.actual_updates.load(Ordering::Relaxed),
429 "expected_updates != actual_updates"
430 );
431 }
432 }
433}