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