moka/future/
builder.rs

1use super::{Cache, FutureExt};
2use crate::{
3    common::{builder_utils, concurrent::Weigher, time::Clock, HousekeeperConfig},
4    notification::{AsyncEvictionListener, ListenerFuture, RemovalCause},
5    policy::{EvictionPolicy, ExpirationPolicy},
6    Expiry,
7};
8
9use std::{
10    collections::hash_map::RandomState,
11    hash::{BuildHasher, Hash},
12    marker::PhantomData,
13    sync::Arc,
14    time::Duration,
15};
16
17/// Builds a [`Cache`][cache-struct] with various configuration knobs.
18///
19/// [cache-struct]: ./struct.Cache.html
20///
21/// # Example: Expirations
22///
23/// ```rust
24/// // Cargo.toml
25/// //
26/// // [dependencies]
27/// // moka = { version = "0.12", features = ["future"] }
28/// // tokio = { version = "1", features = ["rt-multi-thread", "macros" ] }
29/// // futures = "0.3"
30///
31/// use moka::future::Cache;
32/// use std::time::Duration;
33///
34/// #[tokio::main]
35/// async fn main() {
36///     let cache = Cache::builder()
37///         // Max 10,000 entries
38///         .max_capacity(10_000)
39///         // Time to live (TTL): 30 minutes
40///         .time_to_live(Duration::from_secs(30 * 60))
41///         // Time to idle (TTI):  5 minutes
42///         .time_to_idle(Duration::from_secs( 5 * 60))
43///         // Create the cache.
44///         .build();
45///
46///     // This entry will expire after 5 minutes (TTI) if there is no get().
47///     cache.insert(0, "zero").await;
48///
49///     // This get() will extend the entry life for another 5 minutes.
50///     cache.get(&0);
51///
52///     // Even though we keep calling get(), the entry will expire
53///     // after 30 minutes (TTL) from the insert().
54/// }
55/// ```
56///
57#[must_use]
58pub struct CacheBuilder<K, V, C> {
59    name: Option<String>,
60    max_capacity: Option<u64>,
61    initial_capacity: Option<usize>,
62    weigher: Option<Weigher<K, V>>,
63    eviction_policy: EvictionPolicy,
64    eviction_listener: Option<AsyncEvictionListener<K, V>>,
65    expiration_policy: ExpirationPolicy<K, V>,
66    housekeeper_config: HousekeeperConfig,
67    invalidator_enabled: bool,
68    clock: Clock,
69    cache_type: PhantomData<C>,
70}
71
72impl<K, V> Default for CacheBuilder<K, V, Cache<K, V, RandomState>>
73where
74    K: Eq + Hash + Send + Sync + 'static,
75    V: Clone + Send + Sync + 'static,
76{
77    fn default() -> Self {
78        Self {
79            name: None,
80            max_capacity: None,
81            initial_capacity: None,
82            weigher: None,
83            eviction_policy: EvictionPolicy::default(),
84            eviction_listener: None,
85            expiration_policy: ExpirationPolicy::default(),
86            housekeeper_config: HousekeeperConfig::default(),
87            invalidator_enabled: false,
88            clock: Clock::default(),
89            cache_type: PhantomData,
90        }
91    }
92}
93
94impl<K, V> CacheBuilder<K, V, Cache<K, V, RandomState>>
95where
96    K: Eq + Hash + Send + Sync + 'static,
97    V: Clone + Send + Sync + 'static,
98{
99    /// Construct a new `CacheBuilder` that will be used to build a `Cache` holding
100    /// up to `max_capacity` entries.
101    pub fn new(max_capacity: u64) -> Self {
102        Self {
103            max_capacity: Some(max_capacity),
104            ..Self::default()
105        }
106    }
107
108    /// Builds a `Cache<K, V>`.
109    ///
110    /// # Panics
111    ///
112    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
113    /// 1000 years. This is done to protect against overflow when computing key
114    /// expiration.
115    pub fn build(self) -> Cache<K, V, RandomState> {
116        let build_hasher = RandomState::default();
117        let exp = &self.expiration_policy;
118        builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
119        Cache::with_everything(
120            self.name,
121            self.max_capacity,
122            self.initial_capacity,
123            build_hasher,
124            self.weigher,
125            self.eviction_policy,
126            self.eviction_listener,
127            self.expiration_policy,
128            self.housekeeper_config,
129            self.invalidator_enabled,
130            self.clock,
131        )
132    }
133
134    /// Builds a `Cache<K, V, S>` with the given `hasher` of type `S`.
135    ///
136    /// # Examples
137    ///
138    /// This example uses AHash hasher from [AHash][ahash-crate] crate.
139    ///
140    /// [ahash-crate]: https://crates.io/crates/ahash
141    ///
142    /// ```rust
143    /// // Cargo.toml
144    /// // [dependencies]
145    /// // ahash = "0.8"
146    /// // moka = { version = ..., features = ["future"] }
147    /// // tokio = { version = "1", features = ["rt-multi-thread", "macros" ] }
148    ///
149    /// use moka::future::Cache;
150    ///
151    /// #[tokio::main]
152    /// async fn main() {
153    ///     // The type of this cache is: Cache<i32, String, ahash::RandomState>
154    ///     let cache = Cache::builder()
155    ///         .max_capacity(100)
156    ///         .build_with_hasher(ahash::RandomState::default());
157    ///     cache.insert(1, "one".to_string()).await;
158    /// }
159    /// ```
160    ///
161    /// Note: If you need to add a type annotation to your cache, you must use the
162    /// form of `Cache<K, V, S>` instead of `Cache<K, V>`. That `S` is the type of
163    /// the build hasher, and its default is the `RandomState` from
164    /// `std::collections::hash_map` module . If you use a different build hasher,
165    /// you must specify `S` explicitly.
166    ///
167    /// Here is a good example:
168    ///
169    /// ```rust
170    /// # use moka::future::Cache;
171    /// # #[tokio::main]
172    /// # async fn main() {
173    /// # let cache = Cache::builder()
174    /// #     .build_with_hasher(ahash::RandomState::default());
175    /// struct Good {
176    ///     // Specifying the type in Cache<K, V, S> format.
177    ///     cache: Cache<i32, String, ahash::RandomState>,
178    /// }
179    ///
180    /// // Storing the cache from above example. This should compile.
181    /// Good { cache };
182    /// # }
183    /// ```
184    ///
185    /// Here is a bad example. This struct cannot store the above cache because it
186    /// does not specify `S`:
187    ///
188    /// ```compile_fail
189    /// # use moka::future::Cache;
190    /// # #[tokio::main]
191    /// # async fn main() {
192    /// # let cache = Cache::builder()
193    /// #     .build_with_hasher(ahash::RandomState::default());
194    /// struct Bad {
195    ///     // Specifying the type in Cache<K, V> format.
196    ///     cache: Cache<i32, String>,
197    /// }
198    ///
199    /// // This should not compile.
200    /// Bad { cache };
201    /// // => error[E0308]: mismatched types
202    /// //    expected struct `std::collections::hash_map::RandomState`,
203    /// //       found struct `ahash::RandomState`
204    /// # }
205    /// ```
206    ///
207    /// # Panics
208    ///
209    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
210    /// 1000 years. This is done to protect against overflow when computing key
211    /// expiration.
212    pub fn build_with_hasher<S>(self, hasher: S) -> Cache<K, V, S>
213    where
214        S: BuildHasher + Clone + Send + Sync + 'static,
215    {
216        let exp = &self.expiration_policy;
217        builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
218        Cache::with_everything(
219            self.name,
220            self.max_capacity,
221            self.initial_capacity,
222            hasher,
223            self.weigher,
224            self.eviction_policy,
225            self.eviction_listener,
226            self.expiration_policy,
227            self.housekeeper_config,
228            self.invalidator_enabled,
229            self.clock,
230        )
231    }
232}
233
234impl<K, V, C> CacheBuilder<K, V, C> {
235    /// Sets the name of the cache. Currently the name is used for identification
236    /// only in logging messages.
237    pub fn name(self, name: &str) -> Self {
238        Self {
239            name: Some(name.to_string()),
240            ..self
241        }
242    }
243
244    /// Sets the max capacity of the cache.
245    pub fn max_capacity(self, max_capacity: u64) -> Self {
246        Self {
247            max_capacity: Some(max_capacity),
248            ..self
249        }
250    }
251
252    /// Sets the initial capacity (number of entries) of the cache.
253    pub fn initial_capacity(self, number_of_entries: usize) -> Self {
254        Self {
255            initial_capacity: Some(number_of_entries),
256            ..self
257        }
258    }
259
260    /// Sets the eviction (and admission) policy of the cache.
261    ///
262    /// The default policy is TinyLFU. See [`EvictionPolicy`][eviction-policy] for
263    /// more details.
264    ///
265    /// [eviction-policy]: ../policy/struct.EvictionPolicy.html
266    pub fn eviction_policy(self, policy: EvictionPolicy) -> Self {
267        Self {
268            eviction_policy: policy,
269            ..self
270        }
271    }
272
273    /// Sets the weigher closure to the cache.
274    ///
275    /// The closure should take `&K` and `&V` as the arguments and returns a `u32`
276    /// representing the relative size of the entry.
277    pub fn weigher(self, weigher: impl Fn(&K, &V) -> u32 + Send + Sync + 'static) -> Self {
278        Self {
279            weigher: Some(Arc::new(weigher)),
280            ..self
281        }
282    }
283
284    /// Sets the eviction listener closure to the cache. The closure should take
285    /// `Arc<K>`, `V` and [`RemovalCause`][removal-cause] as the arguments.
286    ///
287    /// See [this example][example] for a usage of eviction listener.
288    ///
289    /// # Sync or Async Eviction Listener
290    ///
291    /// The closure can be either synchronous or asynchronous, and `CacheBuilder`
292    /// provides two methods for setting the eviction listener closure:
293    ///
294    /// - If you do not need to `.await` anything in the eviction listener, use this
295    ///   `eviction_listener` method.
296    /// - If you need to `.await` something in the eviction listener, use
297    ///   [`async_eviction_listener`](#method.async_eviction_listener) method
298    ///   instead.
299    ///
300    /// # Panics
301    ///
302    /// It is very important to make the listener closure not to panic. Otherwise,
303    /// the cache will stop calling the listener after a panic. This is an intended
304    /// behavior because the cache cannot know whether it is memory safe or not to
305    /// call the panicked listener again.
306    ///
307    /// [removal-cause]: ../notification/enum.RemovalCause.html
308    /// [example]: ./struct.Cache.html#per-entry-expiration-policy
309    pub fn eviction_listener<F>(self, listener: F) -> Self
310    where
311        F: Fn(Arc<K>, V, RemovalCause) + Send + Sync + 'static,
312    {
313        let async_listener = move |k, v, c| {
314            {
315                listener(k, v, c);
316                std::future::ready(())
317            }
318            .boxed()
319        };
320
321        self.async_eviction_listener(async_listener)
322    }
323
324    /// Sets the eviction listener closure to the cache. The closure should take
325    /// `Arc<K>`, `V` and [`RemovalCause`][removal-cause] as the arguments, and
326    /// return a [`ListenerFuture`][listener-future].
327    ///
328    /// See [this example][example] for a usage of asynchronous eviction listener.
329    ///
330    /// # Sync or Async Eviction Listener
331    ///
332    /// The closure can be either synchronous or asynchronous, and `CacheBuilder`
333    /// provides two methods for setting the eviction listener closure:
334    ///
335    /// - If you do not need to `.await` anything in the eviction listener, use
336    ///   [`eviction_listener`](#method.eviction_listener) method instead.
337    /// - If you need to `.await` something in the eviction listener, use
338    ///   this method.
339    ///
340    /// # Panics
341    ///
342    /// It is very important to make the listener closure not to panic. Otherwise,
343    /// the cache will stop calling the listener after a panic. This is an intended
344    /// behavior because the cache cannot know whether it is memory safe or not to
345    /// call the panicked listener again.
346    ///
347    /// [removal-cause]: ../notification/enum.RemovalCause.html
348    /// [listener-future]: ../notification/type.ListenerFuture.html
349    /// [example]: ./struct.Cache.html#example-eviction-listener
350    pub fn async_eviction_listener<F>(self, listener: F) -> Self
351    where
352        F: Fn(Arc<K>, V, RemovalCause) -> ListenerFuture + Send + Sync + 'static,
353    {
354        Self {
355            eviction_listener: Some(Box::new(listener)),
356            ..self
357        }
358    }
359
360    /// Sets the time to live of the cache.
361    ///
362    /// A cached entry will be expired after the specified duration past from
363    /// `insert`.
364    ///
365    /// # Panics
366    ///
367    /// `CacheBuilder::build*` methods will panic if the given `duration` is longer
368    /// than 1000 years. This is done to protect against overflow when computing key
369    /// expiration.
370    pub fn time_to_live(self, duration: Duration) -> Self {
371        let mut builder = self;
372        builder.expiration_policy.set_time_to_live(duration);
373        builder
374    }
375
376    /// Sets the time to idle of the cache.
377    ///
378    /// A cached entry will be expired after the specified duration past from `get`
379    /// or `insert`.
380    ///
381    /// # Panics
382    ///
383    /// `CacheBuilder::build*` methods will panic if the given `duration` is longer
384    /// than 1000 years. This is done to protect against overflow when computing key
385    /// expiration.
386    pub fn time_to_idle(self, duration: Duration) -> Self {
387        let mut builder = self;
388        builder.expiration_policy.set_time_to_idle(duration);
389        builder
390    }
391
392    /// Sets the given `expiry` to the cache.
393    ///
394    /// See [the example][per-entry-expiration-example] for per-entry expiration
395    /// policy in the `Cache` documentation.
396    ///
397    /// [per-entry-expiration-example]:
398    ///     ./struct.Cache.html#per-entry-expiration-policy
399    pub fn expire_after(self, expiry: impl Expiry<K, V> + Send + Sync + 'static) -> Self {
400        let mut builder = self;
401        builder.expiration_policy.set_expiry(Arc::new(expiry));
402        builder
403    }
404
405    #[cfg(test)]
406    pub(crate) fn housekeeper_config(self, conf: HousekeeperConfig) -> Self {
407        Self {
408            housekeeper_config: conf,
409            ..self
410        }
411    }
412
413    #[cfg(test)]
414    pub(crate) fn clock(self, clock: Clock) -> Self {
415        Self { clock, ..self }
416    }
417
418    /// Enables support for [`Cache::invalidate_entries_if`][cache-invalidate-if]
419    /// method.
420    ///
421    /// The cache will maintain additional internal data structures to support
422    /// `invalidate_entries_if` method.
423    ///
424    /// [cache-invalidate-if]: ./struct.Cache.html#method.invalidate_entries_if
425    pub fn support_invalidation_closures(self) -> Self {
426        Self {
427            invalidator_enabled: true,
428            ..self
429        }
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use super::CacheBuilder;
436
437    use std::time::Duration;
438
439    #[tokio::test]
440    async fn build_cache() {
441        // Cache<char, String>
442        let cache = CacheBuilder::new(100).build();
443        let policy = cache.policy();
444
445        assert_eq!(policy.max_capacity(), Some(100));
446        assert_eq!(policy.time_to_live(), None);
447        assert_eq!(policy.time_to_idle(), None);
448        assert_eq!(policy.num_segments(), 1);
449
450        cache.insert('a', "Alice").await;
451        assert_eq!(cache.get(&'a').await, Some("Alice"));
452
453        let cache = CacheBuilder::new(100)
454            .time_to_live(Duration::from_secs(45 * 60))
455            .time_to_idle(Duration::from_secs(15 * 60))
456            .build();
457        let policy = cache.policy();
458
459        assert_eq!(policy.max_capacity(), Some(100));
460        assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60)));
461        assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60)));
462        assert_eq!(policy.num_segments(), 1);
463
464        cache.insert('a', "Alice").await;
465        assert_eq!(cache.get(&'a').await, Some("Alice"));
466    }
467
468    #[tokio::test]
469    #[should_panic(expected = "time_to_live is longer than 1000 years")]
470    async fn build_cache_too_long_ttl() {
471        let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
472        let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
473        let duration = Duration::from_secs(thousand_years_secs);
474        builder
475            .time_to_live(duration + Duration::from_secs(1))
476            .build();
477    }
478
479    #[tokio::test]
480    #[should_panic(expected = "time_to_idle is longer than 1000 years")]
481    async fn build_cache_too_long_tti() {
482        let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
483        let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
484        let duration = Duration::from_secs(thousand_years_secs);
485        builder
486            .time_to_idle(duration + Duration::from_secs(1))
487            .build();
488    }
489}