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) &mdash; Returns the
144///   duration (or none) after the entry's creation.
145/// - [`expire_after_read`](#method.expire_after_read) &mdash; Returns the duration
146///   (or none)  after its last read.
147/// - [`expire_after_update`](#method.expire_after_update) &mdash; 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` &mdash; A reference to the key of the entry.
163    /// - `value` &mdash; A reference to the value of the entry.
164    /// - `created_at` &mdash; 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)` &mdash; The expiration time is set to
172    ///   `created_at + duration`.
173    /// - Returning `None` &mdash; 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` &mdash; A reference to the key of the entry.
194    /// - `value` &mdash; A reference to the value of the entry.
195    /// - `read_at` &mdash; The time when this entry was read.
196    /// - `duration_until_expiry` &mdash; The remaining duration until the entry
197    ///   expires. (Calculated by `expiration_time - read_at`)
198    /// - `last_modified_at` &mdash; 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)` &mdash; The expiration time is set to
206    ///   `read_at + duration`.
207    /// - Returning `None` &mdash; 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` &mdash; A reference to the key of the entry.
240    /// - `value` &mdash; A reference to the value of the entry.
241    /// - `updated_at` &mdash; The time when this entry was updated.
242    /// - `duration_until_expiry` &mdash; 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)` &mdash; The expiration time is set to
251    ///   `updated_at + duration`.
252    /// - Returning `None` &mdash; 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}