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) — Returns the
148/// duration (or none) after the entry's creation.
149/// - [`expire_after_read`](#method.expire_after_read) — Returns the duration
150/// (or none) after its last read.
151/// - [`expire_after_update`](#method.expire_after_update) — 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` — A reference to the key of the entry.
167 /// - `value` — A reference to the value of the entry.
168 /// - `created_at` — 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)` — The expiration time is set to
176 /// `created_at + duration`.
177 /// - Returning `None` — 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` — A reference to the key of the entry.
198 /// - `value` — A reference to the value of the entry.
199 /// - `read_at` — The time when this entry was read.
200 /// - `duration_until_expiry` — The remaining duration until the entry
201 /// expires. (Calculated by `expiration_time - read_at`)
202 /// - `last_modified_at` — 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)` — The expiration time is set to
210 /// `read_at + duration`.
211 /// - Returning `None` — 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` — A reference to the key of the entry.
244 /// - `value` — A reference to the value of the entry.
245 /// - `updated_at` — The time when this entry was updated.
246 /// - `duration_until_expiry` — 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)` — The expiration time is set to
255 /// `updated_at + duration`.
256 /// - Returning `None` — 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}