moka/
policy.rs

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