moka/sync/
builder.rs

1use super::{Cache, SegmentedCache};
2use crate::{
3    common::{builder_utils, concurrent::Weigher, time::Clock, HousekeeperConfig},
4    notification::{EvictionListener, 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] or [`SegmentedCache`][seg-cache-struct]
18/// with various configuration knobs.
19///
20/// [cache-struct]: ./struct.Cache.html
21/// [seg-cache-struct]: ./struct.SegmentedCache.html
22///
23/// # Example: Expirations
24///
25/// ```rust
26/// use moka::sync::Cache;
27/// use std::time::Duration;
28///
29/// let cache = Cache::builder()
30///     // Max 10,000 entries
31///     .max_capacity(10_000)
32///     // Time to live (TTL): 30 minutes
33///     .time_to_live(Duration::from_secs(30 * 60))
34///     // Time to idle (TTI):  5 minutes
35///     .time_to_idle(Duration::from_secs( 5 * 60))
36///     // Create the cache.
37///     .build();
38///
39/// // This entry will expire after 5 minutes (TTI) if there is no get().
40/// cache.insert(0, "zero");
41///
42/// // This get() will extend the entry life for another 5 minutes.
43/// cache.get(&0);
44///
45/// // Even though we keep calling get(), the entry will expire
46/// // after 30 minutes (TTL) from the insert().
47/// ```
48///
49#[must_use]
50pub struct CacheBuilder<K, V, C> {
51    name: Option<String>,
52    max_capacity: Option<u64>,
53    initial_capacity: Option<usize>,
54    num_segments: Option<usize>,
55    weigher: Option<Weigher<K, V>>,
56    eviction_policy: EvictionPolicy,
57    eviction_listener: Option<EvictionListener<K, V>>,
58    expiration_policy: ExpirationPolicy<K, V>,
59    housekeeper_config: HousekeeperConfig,
60    invalidator_enabled: bool,
61    clock: Clock,
62    cache_type: PhantomData<C>,
63}
64
65impl<K, V> Default for CacheBuilder<K, V, Cache<K, V, RandomState>>
66where
67    K: Eq + Hash + Send + Sync + 'static,
68    V: Clone + Send + Sync + 'static,
69{
70    fn default() -> Self {
71        Self {
72            name: None,
73            max_capacity: None,
74            initial_capacity: None,
75            num_segments: None,
76            weigher: None,
77            eviction_listener: None,
78            eviction_policy: EvictionPolicy::default(),
79            expiration_policy: ExpirationPolicy::default(),
80            housekeeper_config: HousekeeperConfig::default(),
81            invalidator_enabled: false,
82            clock: Clock::default(),
83            cache_type: PhantomData,
84        }
85    }
86}
87
88impl<K, V> CacheBuilder<K, V, Cache<K, V, RandomState>>
89where
90    K: Eq + Hash + Send + Sync + 'static,
91    V: Clone + Send + Sync + 'static,
92{
93    /// Construct a new `CacheBuilder` that will be used to build a `Cache` or
94    /// `SegmentedCache` holding up to `max_capacity` entries.
95    pub fn new(max_capacity: u64) -> Self {
96        Self {
97            max_capacity: Some(max_capacity),
98            ..Default::default()
99        }
100    }
101
102    /// Sets the number of segments of the cache.
103    ///
104    /// # Panics
105    ///
106    /// Panics if `num_segments` is zero.
107    pub fn segments(
108        self,
109        num_segments: usize,
110    ) -> CacheBuilder<K, V, SegmentedCache<K, V, RandomState>> {
111        assert!(num_segments != 0);
112
113        CacheBuilder {
114            name: self.name,
115            max_capacity: self.max_capacity,
116            initial_capacity: self.initial_capacity,
117            num_segments: Some(num_segments),
118            weigher: self.weigher,
119            eviction_policy: self.eviction_policy,
120            eviction_listener: self.eviction_listener,
121            expiration_policy: self.expiration_policy,
122            housekeeper_config: self.housekeeper_config,
123            invalidator_enabled: self.invalidator_enabled,
124            clock: self.clock,
125            cache_type: PhantomData,
126        }
127    }
128
129    /// Builds a `Cache<K, V>`.
130    ///
131    /// If you want to build a `SegmentedCache<K, V>`, call `segments` method before
132    /// calling this method.
133    ///
134    /// # Panics
135    ///
136    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
137    /// 1000 years. This is done to protect against overflow when computing key
138    /// expiration.
139    pub fn build(self) -> Cache<K, V, RandomState> {
140        let build_hasher = RandomState::default();
141        let exp = &self.expiration_policy;
142        builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
143        Cache::with_everything(
144            self.name,
145            self.max_capacity,
146            self.initial_capacity,
147            build_hasher,
148            self.weigher,
149            self.eviction_policy,
150            self.eviction_listener,
151            self.expiration_policy,
152            self.housekeeper_config,
153            self.invalidator_enabled,
154            self.clock,
155        )
156    }
157
158    /// Builds a `Cache<K, V, S>` with the given `hasher` of type `S`.
159    ///
160    /// # Examples
161    ///
162    /// This example uses AHash hasher from [AHash][ahash-crate] crate.
163    ///
164    /// [ahash-crate]: https://crates.io/crates/ahash
165    ///
166    /// ```rust
167    /// // Cargo.toml
168    /// // [dependencies]
169    /// // ahash = "0.8"
170    /// // moka = ...
171    ///
172    /// use moka::sync::Cache;
173    ///
174    /// // The type of this cache is: Cache<i32, String, ahash::RandomState>
175    /// let cache = Cache::builder()
176    ///     .max_capacity(100)
177    ///     .build_with_hasher(ahash::RandomState::default());
178    /// cache.insert(1, "one".to_string());
179    /// ```
180    ///
181    /// Note: If you need to add a type annotation to your cache, you must use the
182    /// form of `Cache<K, V, S>` instead of `Cache<K, V>`. That `S` is the type of
183    /// the build hasher, and its default is the `RandomState` from
184    /// `std::collections::hash_map` module . If you use a different build hasher,
185    /// you must specify `S` explicitly.
186    ///
187    /// Here is a good example:
188    ///
189    /// ```rust
190    /// # use moka::sync::Cache;
191    /// # let cache = Cache::builder()
192    /// #     .build_with_hasher(ahash::RandomState::default());
193    /// struct Good {
194    ///     // Specifying the type in Cache<K, V, S> format.
195    ///     cache: Cache<i32, String, ahash::RandomState>,
196    /// }
197    ///
198    /// // Storing the cache from above example. This should compile.
199    /// Good { cache };
200    /// ```
201    ///
202    /// Here is a bad example. This struct cannot store the above cache because it
203    /// does not specify `S`:
204    ///
205    /// ```compile_fail
206    /// # use moka::sync::Cache;
207    /// # let cache = Cache::builder()
208    /// #     .build_with_hasher(ahash::RandomState::default());
209    /// struct Bad {
210    ///     // Specifying the type in Cache<K, V> format.
211    ///     cache: Cache<i32, String>,
212    /// }
213    ///
214    /// // This should not compile.
215    /// Bad { cache };
216    /// // => error[E0308]: mismatched types
217    /// //    expected struct `std::collections::hash_map::RandomState`,
218    /// //       found struct `ahash::RandomState`
219    /// ```
220    ///
221    /// # Panics
222    ///
223    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
224    /// 1000 years. This is done to protect against overflow when computing key
225    /// expiration.
226    pub fn build_with_hasher<S>(self, hasher: S) -> Cache<K, V, S>
227    where
228        S: BuildHasher + Clone + Send + Sync + 'static,
229    {
230        let exp = &self.expiration_policy;
231        builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
232        Cache::with_everything(
233            self.name,
234            self.max_capacity,
235            self.initial_capacity,
236            hasher,
237            self.weigher,
238            self.eviction_policy,
239            self.eviction_listener,
240            self.expiration_policy,
241            self.housekeeper_config,
242            self.invalidator_enabled,
243            self.clock,
244        )
245    }
246}
247
248impl<K, V> CacheBuilder<K, V, SegmentedCache<K, V, RandomState>>
249where
250    K: Eq + Hash + Send + Sync + 'static,
251    V: Clone + Send + Sync + 'static,
252{
253    /// Builds a `SegmentedCache<K, V>`.
254    ///
255    /// If you want to build a `Cache<K, V>`, do not call `segments` method before
256    /// calling this method.
257    ///
258    /// # Panics
259    ///
260    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
261    /// 1000 years. This is done to protect against overflow when computing key
262    /// expiration.
263    pub fn build(self) -> SegmentedCache<K, V, RandomState> {
264        let build_hasher = RandomState::default();
265        let exp = &self.expiration_policy;
266        builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
267        SegmentedCache::with_everything(
268            self.name,
269            self.max_capacity,
270            self.initial_capacity,
271            self.num_segments.unwrap(),
272            build_hasher,
273            self.weigher,
274            self.eviction_policy,
275            self.eviction_listener,
276            self.expiration_policy,
277            self.housekeeper_config,
278            self.invalidator_enabled,
279            self.clock,
280        )
281    }
282
283    /// Builds a `SegmentedCache<K, V, S>` with the given `hasher`.
284    ///
285    ///
286    /// # Examples
287    ///
288    /// This example uses AHash hasher from [AHash][ahash-crate] crate.
289    ///
290    /// [ahash-crate]: https://crates.io/crates/ahash
291    ///
292    /// ```rust
293    /// // Cargo.toml
294    /// // [dependencies]
295    /// // ahash = "0.8"
296    /// // moka = ...
297    ///
298    /// use moka::sync::SegmentedCache;
299    ///
300    /// // The type of this cache is: SegmentedCache<i32, String, ahash::RandomState>
301    /// let cache = SegmentedCache::builder(4)
302    ///     .max_capacity(100)
303    ///     .build_with_hasher(ahash::RandomState::default());
304    /// cache.insert(1, "one".to_string());
305    /// ```
306    ///
307    /// Note: If you need to add a type annotation to your cache, you must use the
308    /// form of `SegmentedCache<K, V, S>` instead of `SegmentedCache<K, V>`. That `S`
309    /// is the type of the build hasher, whose default is the `RandomState` from
310    /// `std::collections::hash_map` module . If you use a different build hasher,
311    /// you must specify `S` explicitly.
312    ///
313    /// Here is a good example:
314    ///
315    /// ```rust
316    /// # use moka::sync::SegmentedCache;
317    /// # let cache = SegmentedCache::builder(4)
318    /// #     .build_with_hasher(ahash::RandomState::default());
319    /// struct Good {
320    ///     // Specifying the type in SegmentedCache<K, V, S> format.
321    ///     cache: SegmentedCache<i32, String, ahash::RandomState>,
322    /// }
323    ///
324    /// // Storing the cache from above example. This should compile.
325    /// Good { cache };
326    /// ```
327    ///
328    /// Here is a bad example. This struct cannot store the above cache because it
329    /// does not specify `S`:
330    ///
331    /// ```compile_fail
332    /// # use moka::sync::SegmentedCache;
333    /// # let cache = SegmentedCache::builder(4)
334    /// #     .build_with_hasher(ahash::RandomState::default());
335    /// struct Bad {
336    ///     // Specifying the type in SegmentedCache<K, V> format.
337    ///     cache: SegmentedCache<i32, String>,
338    /// }
339    ///
340    /// // This should not compile.
341    /// Bad { cache };
342    /// // => error[E0308]: mismatched types
343    /// //    expected struct `std::collections::hash_map::RandomState`,
344    /// //       found struct `ahash::RandomState`
345    /// ```
346    ///
347    /// # Panics
348    ///
349    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
350    /// 1000 years. This is done to protect against overflow when computing key
351    /// expiration.
352    pub fn build_with_hasher<S>(self, hasher: S) -> SegmentedCache<K, V, S>
353    where
354        S: BuildHasher + Clone + Send + Sync + 'static,
355    {
356        let exp = &self.expiration_policy;
357        builder_utils::ensure_expirations_or_panic(exp.time_to_live(), exp.time_to_idle());
358        SegmentedCache::with_everything(
359            self.name,
360            self.max_capacity,
361            self.initial_capacity,
362            self.num_segments.unwrap(),
363            hasher,
364            self.weigher,
365            self.eviction_policy,
366            self.eviction_listener,
367            self.expiration_policy,
368            self.housekeeper_config,
369            self.invalidator_enabled,
370            self.clock,
371        )
372    }
373}
374
375impl<K, V, C> CacheBuilder<K, V, C> {
376    /// Sets the name of the cache. Currently the name is used for identification
377    /// only in logging messages.
378    pub fn name(self, name: &str) -> Self {
379        Self {
380            name: Some(name.to_string()),
381            ..self
382        }
383    }
384
385    /// Sets the max capacity of the cache.
386    pub fn max_capacity(self, max_capacity: u64) -> Self {
387        Self {
388            max_capacity: Some(max_capacity),
389            ..self
390        }
391    }
392
393    /// Sets the initial capacity (number of entries) of the cache.
394    pub fn initial_capacity(self, number_of_entries: usize) -> Self {
395        Self {
396            initial_capacity: Some(number_of_entries),
397            ..self
398        }
399    }
400
401    /// Sets the eviction (and admission) policy of the cache.
402    ///
403    /// The default policy is TinyLFU. See [`EvictionPolicy`][eviction-policy] for
404    /// more details.
405    ///
406    /// [eviction-policy]: ../policy/struct.EvictionPolicy.html
407    pub fn eviction_policy(self, policy: EvictionPolicy) -> Self {
408        Self {
409            eviction_policy: policy,
410            ..self
411        }
412    }
413
414    /// Sets the weigher closure to the cache.
415    ///
416    /// The closure should take `&K` and `&V` as the arguments and returns a `u32`
417    /// representing the relative size of the entry.
418    pub fn weigher(self, weigher: impl Fn(&K, &V) -> u32 + Send + Sync + 'static) -> Self {
419        Self {
420            weigher: Some(Arc::new(weigher)),
421            ..self
422        }
423    }
424
425    /// Sets the eviction listener closure to the cache.
426    ///
427    /// The closure should take `Arc<K>`, `V` and [`RemovalCause`][removal-cause] as
428    /// the arguments.
429    ///
430    /// # Panics
431    ///
432    /// It is very important to make the listener closure not to panic. Otherwise,
433    /// the cache will stop calling the listener after a panic. This is an intended
434    /// behavior because the cache cannot know whether it is memory safe or not to
435    /// call the panicked listener again.
436    ///
437    /// [removal-cause]: ../notification/enum.RemovalCause.html
438    pub fn eviction_listener(
439        self,
440        listener: impl Fn(Arc<K>, V, RemovalCause) + Send + Sync + 'static,
441    ) -> Self {
442        Self {
443            eviction_listener: Some(Arc::new(listener)),
444            ..self
445        }
446    }
447
448    /// Sets the time to live of the cache.
449    ///
450    /// A cached entry will be expired after the specified duration past from
451    /// `insert`.
452    ///
453    /// # Panics
454    ///
455    /// `CacheBuilder::build*` methods will panic if the given `duration` is longer
456    /// than 1000 years. This is done to protect against overflow when computing key
457    /// expiration.
458    pub fn time_to_live(self, duration: Duration) -> Self {
459        let mut builder = self;
460        builder.expiration_policy.set_time_to_live(duration);
461        builder
462    }
463
464    /// Sets the time to idle of the cache.
465    ///
466    /// A cached entry will be expired after the specified duration past from `get`
467    /// or `insert`.
468    ///
469    /// # Panics
470    ///
471    /// `CacheBuilder::build*` methods will panic if the given `duration` is longer
472    /// than 1000 years. This is done to protect against overflow when computing key
473    /// expiration.
474    pub fn time_to_idle(self, duration: Duration) -> Self {
475        let mut builder = self;
476        builder.expiration_policy.set_time_to_idle(duration);
477        builder
478    }
479
480    /// Sets the given `expiry` to the cache.
481    ///
482    /// See [the example][per-entry-expiration-example] for per-entry expiration
483    /// policy in the `Cache` documentation.
484    ///
485    /// [per-entry-expiration-example]:
486    ///     ./struct.Cache.html#per-entry-expiration-policy
487    pub fn expire_after(self, expiry: impl Expiry<K, V> + Send + Sync + 'static) -> Self {
488        let mut builder = self;
489        builder.expiration_policy.set_expiry(Arc::new(expiry));
490        builder
491    }
492
493    #[cfg(test)]
494    pub(crate) fn housekeeper_config(self, conf: HousekeeperConfig) -> Self {
495        Self {
496            housekeeper_config: conf,
497            ..self
498        }
499    }
500
501    #[cfg(test)]
502    pub(crate) fn clock(self, clock: Clock) -> Self {
503        Self { clock, ..self }
504    }
505
506    /// Enables support for [`Cache::invalidate_entries_if`][cache-invalidate-if]
507    /// method.
508    ///
509    /// The cache will maintain additional internal data structures to support
510    /// `invalidate_entries_if` method.
511    ///
512    /// [cache-invalidate-if]: ./struct.Cache.html#method.invalidate_entries_if
513    pub fn support_invalidation_closures(self) -> Self {
514        Self {
515            invalidator_enabled: true,
516            ..self
517        }
518    }
519}
520
521#[cfg(test)]
522mod tests {
523    use super::CacheBuilder;
524
525    use std::time::Duration;
526
527    #[test]
528    fn build_cache() {
529        // Cache<char, String>
530        let cache = CacheBuilder::new(100).build();
531        let policy = cache.policy();
532
533        assert_eq!(policy.max_capacity(), Some(100));
534        assert_eq!(policy.time_to_live(), None);
535        assert_eq!(policy.time_to_idle(), None);
536        assert_eq!(policy.num_segments(), 1);
537
538        cache.insert('a', "Alice");
539        assert_eq!(cache.get(&'a'), Some("Alice"));
540
541        let cache = CacheBuilder::new(100)
542            .time_to_live(Duration::from_secs(45 * 60))
543            .time_to_idle(Duration::from_secs(15 * 60))
544            .build();
545        let config = cache.policy();
546
547        assert_eq!(config.max_capacity(), Some(100));
548        assert_eq!(config.time_to_live(), Some(Duration::from_secs(45 * 60)));
549        assert_eq!(config.time_to_idle(), Some(Duration::from_secs(15 * 60)));
550        assert_eq!(config.num_segments(), 1);
551
552        cache.insert('a', "Alice");
553        assert_eq!(cache.get(&'a'), Some("Alice"));
554    }
555
556    #[test]
557    fn build_segmented_cache() {
558        // SegmentCache<char, String>
559        let cache = CacheBuilder::new(100).segments(15).build();
560        let policy = cache.policy();
561
562        assert_eq!(policy.max_capacity(), Some(100));
563        assert!(policy.time_to_live().is_none());
564        assert!(policy.time_to_idle().is_none());
565        assert_eq!(policy.num_segments(), 16_usize.next_power_of_two());
566
567        cache.insert('b', "Bob");
568        assert_eq!(cache.get(&'b'), Some("Bob"));
569
570        let listener = move |_key, _value, _cause| ();
571
572        let builder = CacheBuilder::new(400)
573            .time_to_live(Duration::from_secs(45 * 60))
574            .time_to_idle(Duration::from_secs(15 * 60))
575            .eviction_listener(listener)
576            .name("tracked_sessions")
577            // Call segments() at the end to check all field values in the current
578            // builder struct are copied to the new builder:
579            // https://github.com/moka-rs/moka/issues/207
580            .segments(24);
581
582        assert!(builder.eviction_listener.is_some());
583
584        let cache = builder.build();
585        let policy = cache.policy();
586
587        assert_eq!(policy.max_capacity(), Some(400));
588        assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60)));
589        assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60)));
590        assert_eq!(policy.num_segments(), 24_usize.next_power_of_two());
591        assert_eq!(cache.name(), Some("tracked_sessions"));
592
593        cache.insert('b', "Bob");
594        assert_eq!(cache.get(&'b'), Some("Bob"));
595    }
596
597    #[test]
598    #[should_panic(expected = "time_to_live is longer than 1000 years")]
599    fn build_cache_too_long_ttl() {
600        let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
601        let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
602        let duration = Duration::from_secs(thousand_years_secs);
603        builder
604            .time_to_live(duration + Duration::from_secs(1))
605            .build();
606    }
607
608    #[test]
609    #[should_panic(expected = "time_to_idle is longer than 1000 years")]
610    fn build_cache_too_long_tti() {
611        let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
612        let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
613        let duration = Duration::from_secs(thousand_years_secs);
614        builder
615            .time_to_idle(duration + Duration::from_secs(1))
616            .build();
617    }
618}