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, PartialEq, Eq)]
129pub(crate) enum EvictionPolicyConfig {
130    TinyLfu,
131    Lru,
132}
133
134impl Default for EvictionPolicyConfig {
135    fn default() -> Self {
136        Self::TinyLfu
137    }
138}
139
140/// Calculates when cache entries expire. A single expiration time is retained on
141/// each entry so that the lifetime of an entry may be extended or reduced by
142/// subsequent evaluations.
143///
144/// `Expiry` trait provides three methods. They specify the expiration time of an
145/// entry by returning a `Some(duration)` until the entry expires:
146///
147/// - [`expire_after_create`](#method.expire_after_create) &mdash; Returns the
148///   duration (or none) after the entry's creation.
149/// - [`expire_after_read`](#method.expire_after_read) &mdash; Returns the duration
150///   (or none)  after its last read.
151/// - [`expire_after_update`](#method.expire_after_update) &mdash; Returns the
152///   duration (or none)  after its last update.
153///
154/// The default implementations are provided that return `None` (no expiration) or
155/// `current_duration: Option<Instant>` (not modify the current expiration time).
156/// Override some of them as you need.
157///
158pub trait Expiry<K, V> {
159    /// Specifies that the entry should be automatically removed from the cache once
160    /// the duration has elapsed after the entry's creation. This method is called
161    /// for cache write methods such as `insert` and `get_with` but only when the key
162    /// was not present in the cache.
163    ///
164    /// # Parameters
165    ///
166    /// - `key` &mdash; A reference to the key of the entry.
167    /// - `value` &mdash; A reference to the value of the entry.
168    /// - `created_at` &mdash; The time when this entry was inserted.
169    ///
170    /// # Return value
171    ///
172    /// The returned `Option<Duration>` is used to set the expiration time of the
173    /// entry.
174    ///
175    /// - Returning `Some(duration)` &mdash; The expiration time is set to
176    ///   `created_at + duration`.
177    /// - Returning `None` &mdash; The expiration time is cleared (no expiration).
178    ///   - This is the value that the default implementation returns.
179    ///
180    /// # Notes on `time_to_live` and `time_to_idle` policies
181    ///
182    /// When the cache is configured with `time_to_live` and/or `time_to_idle`
183    /// policies, the entry will be evicted after the earliest of the expiration time
184    /// returned by this expiry, the `time_to_live` and `time_to_idle` policies.
185    #[allow(unused_variables)]
186    fn expire_after_create(&self, key: &K, value: &V, created_at: Instant) -> Option<Duration> {
187        None
188    }
189
190    /// Specifies that the entry should be automatically removed from the cache once
191    /// the duration has elapsed after its last read. This method is called for cache
192    /// read methods such as `get` and `get_with` but only when the key is present in
193    /// the cache.
194    ///
195    /// # Parameters
196    ///
197    /// - `key` &mdash; A reference to the key of the entry.
198    /// - `value` &mdash; A reference to the value of the entry.
199    /// - `read_at` &mdash; The time when this entry was read.
200    /// - `duration_until_expiry` &mdash; The remaining duration until the entry
201    ///   expires. (Calculated by `expiration_time - read_at`)
202    /// - `last_modified_at` &mdash; The time when this entry was created or updated.
203    ///
204    /// # Return value
205    ///
206    /// The returned `Option<Duration>` is used to set the expiration time of the
207    /// entry.
208    ///
209    /// - Returning `Some(duration)` &mdash; The expiration time is set to
210    ///   `read_at + duration`.
211    /// - Returning `None` &mdash; The expiration time is cleared (no expiration).
212    /// - Returning `duration_until_expiry` will not modify the expiration time.
213    ///   - This is the value that the default implementation returns.
214    ///
215    /// # Notes on `time_to_live` and `time_to_idle` policies
216    ///
217    /// When the cache is configured with `time_to_live` and/or `time_to_idle`
218    /// policies, then:
219    ///
220    /// - The entry will be evicted after the earliest of the expiration time
221    ///   returned by this expiry, the `time_to_live` and `time_to_idle` policies.
222    /// - The `duration_until_expiry` takes in account the `time_to_live` and
223    ///   `time_to_idle` policies.
224    #[allow(unused_variables)]
225    fn expire_after_read(
226        &self,
227        key: &K,
228        value: &V,
229        read_at: Instant,
230        duration_until_expiry: Option<Duration>,
231        last_modified_at: Instant,
232    ) -> Option<Duration> {
233        duration_until_expiry
234    }
235
236    /// Specifies that the entry should be automatically removed from the cache once
237    /// the duration has elapsed after the replacement of its value. This method is
238    /// called for cache write methods such as `insert` but only when the key is
239    /// already present in the cache.
240    ///
241    /// # Parameters
242    ///
243    /// - `key` &mdash; A reference to the key of the entry.
244    /// - `value` &mdash; A reference to the value of the entry.
245    /// - `updated_at` &mdash; The time when this entry was updated.
246    /// - `duration_until_expiry` &mdash; The remaining duration until the entry
247    ///   expires. (Calculated by `expiration_time - updated_at`)
248    ///
249    /// # Return value
250    ///
251    /// The returned `Option<Duration>` is used to set the expiration time of the
252    /// entry.
253    ///
254    /// - Returning `Some(duration)` &mdash; The expiration time is set to
255    ///   `updated_at + duration`.
256    /// - Returning `None` &mdash; The expiration time is cleared (no expiration).
257    /// - Returning `duration_until_expiry` will not modify the expiration time.
258    ///   - This is the value that the default implementation returns.
259    ///
260    /// # Notes on `time_to_live` and `time_to_idle` policies
261    ///
262    /// When the cache is configured with `time_to_live` and/or `time_to_idle`
263    /// policies, then:
264    ///
265    /// - The entry will be evicted after the earliest of the expiration time
266    ///   returned by this expiry, the `time_to_live` and `time_to_idle` policies.
267    /// - The `duration_until_expiry` takes in account the `time_to_live` and
268    ///   `time_to_idle` policies.
269    #[allow(unused_variables)]
270    fn expire_after_update(
271        &self,
272        key: &K,
273        value: &V,
274        updated_at: Instant,
275        duration_until_expiry: Option<Duration>,
276    ) -> Option<Duration> {
277        duration_until_expiry
278    }
279}
280
281pub(crate) struct ExpirationPolicy<K, V> {
282    time_to_live: Option<Duration>,
283    time_to_idle: Option<Duration>,
284    expiry: Option<Arc<dyn Expiry<K, V> + Send + Sync + 'static>>,
285}
286
287impl<K, V> Default for ExpirationPolicy<K, V> {
288    fn default() -> Self {
289        Self {
290            time_to_live: None,
291            time_to_idle: None,
292            expiry: None,
293        }
294    }
295}
296
297impl<K, V> Clone for ExpirationPolicy<K, V> {
298    fn clone(&self) -> Self {
299        Self {
300            time_to_live: self.time_to_live,
301            time_to_idle: self.time_to_idle,
302            expiry: self.expiry.clone(),
303        }
304    }
305}
306
307impl<K, V> ExpirationPolicy<K, V> {
308    #[cfg(test)]
309    pub(crate) fn new(
310        time_to_live: Option<Duration>,
311        time_to_idle: Option<Duration>,
312        expiry: Option<Arc<dyn Expiry<K, V> + Send + Sync + 'static>>,
313    ) -> Self {
314        Self {
315            time_to_live,
316            time_to_idle,
317            expiry,
318        }
319    }
320
321    /// Returns the `time_to_live` of the cache.
322    pub(crate) fn time_to_live(&self) -> Option<Duration> {
323        self.time_to_live
324    }
325
326    pub(crate) fn set_time_to_live(&mut self, duration: Duration) {
327        self.time_to_live = Some(duration);
328    }
329
330    /// Returns the `time_to_idle` of the cache.
331    pub(crate) fn time_to_idle(&self) -> Option<Duration> {
332        self.time_to_idle
333    }
334
335    pub(crate) fn set_time_to_idle(&mut self, duration: Duration) {
336        self.time_to_idle = Some(duration);
337    }
338
339    pub(crate) fn expiry(&self) -> Option<Arc<dyn Expiry<K, V> + Send + Sync + 'static>> {
340        self.expiry.clone()
341    }
342
343    pub(crate) fn set_expiry(&mut self, expiry: Arc<dyn Expiry<K, V> + Send + Sync + 'static>) {
344        self.expiry = Some(expiry);
345    }
346}
347
348#[cfg(test)]
349pub(crate) mod test_utils {
350    use std::sync::atomic::{AtomicU8, Ordering};
351
352    #[derive(Default)]
353    pub(crate) struct ExpiryCallCounters {
354        expected_creations: AtomicU8,
355        expected_reads: AtomicU8,
356        expected_updates: AtomicU8,
357        actual_creations: AtomicU8,
358        actual_reads: AtomicU8,
359        actual_updates: AtomicU8,
360    }
361
362    impl ExpiryCallCounters {
363        pub(crate) fn incl_expected_creations(&self) {
364            self.expected_creations.fetch_add(1, Ordering::Relaxed);
365        }
366
367        pub(crate) fn incl_expected_reads(&self) {
368            self.expected_reads.fetch_add(1, Ordering::Relaxed);
369        }
370
371        pub(crate) fn incl_expected_updates(&self) {
372            self.expected_updates.fetch_add(1, Ordering::Relaxed);
373        }
374
375        pub(crate) fn incl_actual_creations(&self) {
376            self.actual_creations.fetch_add(1, Ordering::Relaxed);
377        }
378
379        pub(crate) fn incl_actual_reads(&self) {
380            self.actual_reads.fetch_add(1, Ordering::Relaxed);
381        }
382
383        pub(crate) fn incl_actual_updates(&self) {
384            self.actual_updates.fetch_add(1, Ordering::Relaxed);
385        }
386
387        pub(crate) fn verify(&self) {
388            assert_eq!(
389                self.expected_creations.load(Ordering::Relaxed),
390                self.actual_creations.load(Ordering::Relaxed),
391                "expected_creations != actual_creations"
392            );
393            assert_eq!(
394                self.expected_reads.load(Ordering::Relaxed),
395                self.actual_reads.load(Ordering::Relaxed),
396                "expected_reads != actual_reads"
397            );
398            assert_eq!(
399                self.expected_updates.load(Ordering::Relaxed),
400                self.actual_updates.load(Ordering::Relaxed),
401                "expected_updates != actual_updates"
402            );
403        }
404    }
405}