mini_moka/sync/
cache.rs

1use super::{base_cache::BaseCache, CacheBuilder, ConcurrentCacheExt, EntryRef, Iter};
2use crate::{
3    common::{
4        concurrent::{
5            constants::{MAX_SYNC_REPEATS, WRITE_RETRY_INTERVAL_MICROS},
6            housekeeper::{Housekeeper, InnerSync},
7            Weigher, WriteOp,
8        },
9        time::Instant,
10    },
11    Policy,
12};
13
14use crossbeam_channel::{Sender, TrySendError};
15use std::{
16    borrow::Borrow,
17    collections::hash_map::RandomState,
18    fmt,
19    hash::{BuildHasher, Hash},
20    sync::Arc,
21    time::Duration,
22};
23
24/// A thread-safe concurrent in-memory cache built upon [`dashmap::DashMap`][dashmap].
25///
26/// The `Cache` uses `DashMap` as the central key-value storage. It performs a
27/// best-effort bounding of the map using an entry replacement algorithm to determine
28/// which entries to evict when the capacity is exceeded.
29///
30/// To use this cache, enable a crate feature called "dash" in your Cargo.toml.
31/// Please note that the API of `dash` cache will _be changed very often_ in next few
32/// releases as this is yet an experimental component.
33///
34/// # Examples
35///
36/// Cache entries are manually added using [`insert`](#method.insert) method, and are
37/// stored in the cache until either evicted or manually invalidated.
38///
39/// Here's an example of reading and updating a cache by using multiple threads:
40///
41/// ```rust
42/// use mini_moka::sync::Cache;
43///
44/// use std::thread;
45///
46/// fn value(n: usize) -> String {
47///     format!("value {}", n)
48/// }
49///
50/// const NUM_THREADS: usize = 16;
51/// const NUM_KEYS_PER_THREAD: usize = 64;
52///
53/// // Create a cache that can store up to 10,000 entries.
54/// let cache = Cache::new(10_000);
55///
56/// // Spawn threads and read and update the cache simultaneously.
57/// let threads: Vec<_> = (0..NUM_THREADS)
58///     .map(|i| {
59///         // To share the same cache across the threads, clone it.
60///         // This is a cheap operation.
61///         let my_cache = cache.clone();
62///         let start = i * NUM_KEYS_PER_THREAD;
63///         let end = (i + 1) * NUM_KEYS_PER_THREAD;
64///
65///         thread::spawn(move || {
66///             // Insert 64 entries. (NUM_KEYS_PER_THREAD = 64)
67///             for key in start..end {
68///                 my_cache.insert(key, value(key));
69///                 // get() returns Option<String>, a clone of the stored value.
70///                 assert_eq!(my_cache.get(&key), Some(value(key)));
71///             }
72///
73///             // Invalidate every 4 element of the inserted entries.
74///             for key in (start..end).step_by(4) {
75///                 my_cache.invalidate(&key);
76///             }
77///         })
78///     })
79///     .collect();
80///
81/// // Wait for all threads to complete.
82/// threads.into_iter().for_each(|t| t.join().expect("Failed"));
83///
84/// // Verify the result.
85/// for key in 0..(NUM_THREADS * NUM_KEYS_PER_THREAD) {
86///     if key % 4 == 0 {
87///         assert_eq!(cache.get(&key), None);
88///     } else {
89///         assert_eq!(cache.get(&key), Some(value(key)));
90///     }
91/// }
92/// ```
93///
94/// # Avoiding to clone the value at `get`
95///
96/// The return type of `get` method is `Option<V>` instead of `Option<&V>`. Every
97/// time `get` is called for an existing key, it creates a clone of the stored value
98/// `V` and returns it. This is because the `Cache` allows concurrent updates from
99/// threads so a value stored in the cache can be dropped or replaced at any time by
100/// any other thread. `get` cannot return a reference `&V` as it is impossible to
101/// guarantee the value outlives the reference.
102///
103/// If you want to store values that will be expensive to clone, wrap them by
104/// `std::sync::Arc` before storing in a cache. [`Arc`][rustdoc-std-arc] is a
105/// thread-safe reference-counted pointer and its `clone()` method is cheap.
106///
107/// [rustdoc-std-arc]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html
108///
109/// # Size-based Eviction
110///
111/// ```rust
112/// use std::convert::TryInto;
113/// use mini_moka::sync::Cache;
114///
115/// // Evict based on the number of entries in the cache.
116/// let cache = Cache::builder()
117///     // Up to 10,000 entries.
118///     .max_capacity(10_000)
119///     // Create the cache.
120///     .build();
121/// cache.insert(1, "one".to_string());
122///
123/// // Evict based on the byte length of strings in the cache.
124/// let cache = Cache::builder()
125///     // A weigher closure takes &K and &V and returns a u32
126///     // representing the relative size of the entry.
127///     .weigher(|_key, value: &String| -> u32 {
128///         value.len().try_into().unwrap_or(u32::MAX)
129///     })
130///     // This cache will hold up to 32MiB of values.
131///     .max_capacity(32 * 1024 * 1024)
132///     .build();
133/// cache.insert(2, "two".to_string());
134/// ```
135///
136/// If your cache should not grow beyond a certain size, use the `max_capacity`
137/// method of the [`CacheBuilder`][builder-struct] to set the upper bound. The cache
138/// will try to evict entries that have not been used recently or very often.
139///
140/// At the cache creation time, a weigher closure can be set by the `weigher` method
141/// of the `CacheBuilder`. A weigher closure takes `&K` and `&V` as the arguments and
142/// returns a `u32` representing the relative size of the entry:
143///
144/// - If the `weigher` is _not_ set, the cache will treat each entry has the same
145///   size of `1`. This means the cache will be bounded by the number of entries.
146/// - If the `weigher` is set, the cache will call the weigher to calculate the
147///   weighted size (relative size) on an entry. This means the cache will be bounded
148///   by the total weighted size of entries.
149///
150/// Note that weighted sizes are not used when making eviction selections.
151///
152/// [builder-struct]: ./struct.CacheBuilder.html
153///
154/// # Time-based Expirations
155///
156/// `Cache` supports the following expiration policies:
157///
158/// - **Time to live**: A cached entry will be expired after the specified duration
159///   past from `insert`.
160/// - **Time to idle**: A cached entry will be expired after the specified duration
161///   past from `get` or `insert`.
162///
163/// ```rust
164/// use mini_moka::sync::Cache;
165/// use std::time::Duration;
166///
167/// let cache = Cache::builder()
168///     // Time to live (TTL): 30 minutes
169///     .time_to_live(Duration::from_secs(30 * 60))
170///     // Time to idle (TTI):  5 minutes
171///     .time_to_idle(Duration::from_secs( 5 * 60))
172///     // Create the cache.
173///     .build();
174///
175/// // This entry will expire after 5 minutes (TTI) if there is no get().
176/// cache.insert(0, "zero");
177///
178/// // This get() will extend the entry life for another 5 minutes.
179/// cache.get(&0);
180///
181/// // Even though we keep calling get(), the entry will expire
182/// // after 30 minutes (TTL) from the insert().
183/// ```
184///
185/// # Thread Safety
186///
187/// All methods provided by the `Cache` are considered thread-safe, and can be safely
188/// accessed by multiple concurrent threads.
189///
190/// - `Cache<K, V, S>` requires trait bounds `Send`, `Sync` and `'static` for `K`
191///   (key), `V` (value) and `S` (hasher state).
192/// - `Cache<K, V, S>` will implement `Send` and `Sync`.
193///
194/// # Sharing a cache across threads
195///
196/// To share a cache across threads, do one of the followings:
197///
198/// - Create a clone of the cache by calling its `clone` method and pass it to other
199///   thread.
200/// - Wrap the cache by a `sync::OnceCell` or `sync::Lazy` from
201///   [once_cell][once-cell-crate] create, and set it to a `static` variable.
202///
203/// Cloning is a cheap operation for `Cache` as it only creates thread-safe
204/// reference-counted pointers to the internal data structures.
205///
206/// [once-cell-crate]: https://crates.io/crates/once_cell
207///
208/// # Hashing Algorithm
209///
210/// By default, `Cache` uses a hashing algorithm selected to provide resistance
211/// against HashDoS attacks. It will be the same one used by
212/// `std::collections::HashMap`, which is currently SipHash 1-3.
213///
214/// While SipHash's performance is very competitive for medium sized keys, other
215/// hashing algorithms will outperform it for small keys such as integers as well as
216/// large keys such as long strings. However those algorithms will typically not
217/// protect against attacks such as HashDoS.
218///
219/// The hashing algorithm can be replaced on a per-`Cache` basis using the
220/// [`build_with_hasher`][build-with-hasher-method] method of the
221/// `CacheBuilder`. Many alternative algorithms are available on crates.io, such
222/// as the [aHash][ahash-crate] crate.
223///
224/// [build-with-hasher-method]: ./struct.CacheBuilder.html#method.build_with_hasher
225/// [ahash-crate]: https://crates.io/crates/ahash
226///
227pub struct Cache<K, V, S = RandomState> {
228    base: BaseCache<K, V, S>,
229}
230
231// TODO: https://github.com/moka-rs/moka/issues/54
232#[allow(clippy::non_send_fields_in_send_ty)]
233unsafe impl<K, V, S> Send for Cache<K, V, S>
234where
235    K: Send + Sync,
236    V: Send + Sync,
237    S: Send,
238{
239}
240
241unsafe impl<K, V, S> Sync for Cache<K, V, S>
242where
243    K: Send + Sync,
244    V: Send + Sync,
245    S: Sync,
246{
247}
248
249// NOTE: We cannot do `#[derive(Clone)]` because it will add `Clone` bound to `K`.
250impl<K, V, S> Clone for Cache<K, V, S> {
251    /// Makes a clone of this shared cache.
252    ///
253    /// This operation is cheap as it only creates thread-safe reference counted
254    /// pointers to the shared internal data structures.
255    fn clone(&self) -> Self {
256        Self {
257            base: self.base.clone(),
258        }
259    }
260}
261
262impl<K, V, S> fmt::Debug for Cache<K, V, S>
263where
264    K: Eq + Hash + fmt::Debug,
265    V: fmt::Debug,
266    S: BuildHasher + Clone,
267{
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        let mut d_map = f.debug_map();
270
271        for r in self.iter() {
272            let (k, v) = r.pair();
273            d_map.entry(k, v);
274        }
275
276        d_map.finish()
277    }
278}
279
280impl<K, V> Cache<K, V, RandomState>
281where
282    K: Hash + Eq + Send + Sync + 'static,
283    V: Clone + Send + Sync + 'static,
284{
285    /// Constructs a new `Cache<K, V>` that will store up to the `max_capacity`.
286    ///
287    /// To adjust various configuration knobs such as `initial_capacity` or
288    /// `time_to_live`, use the [`CacheBuilder`][builder-struct].
289    ///
290    /// [builder-struct]: ./struct.CacheBuilder.html
291    pub fn new(max_capacity: u64) -> Self {
292        let build_hasher = RandomState::default();
293        Self::with_everything(Some(max_capacity), None, build_hasher, None, None, None)
294    }
295
296    /// Returns a [`CacheBuilder`][builder-struct], which can builds a `Cache` with
297    /// various configuration knobs.
298    ///
299    /// [builder-struct]: ./struct.CacheBuilder.html
300    pub fn builder() -> CacheBuilder<K, V, Cache<K, V, RandomState>> {
301        CacheBuilder::default()
302    }
303}
304
305impl<K, V, S> Cache<K, V, S> {
306    /// Returns a read-only cache policy of this cache.
307    ///
308    /// At this time, cache policy cannot be modified after cache creation.
309    /// A future version may support to modify it.
310    pub fn policy(&self) -> Policy {
311        self.base.policy()
312    }
313
314    /// Returns an approximate number of entries in this cache.
315    ///
316    /// The value returned is _an estimate_; the actual count may differ if there are
317    /// concurrent insertions or removals, or if some entries are pending removal due
318    /// to expiration. This inaccuracy can be mitigated by performing a `sync()`
319    /// first.
320    ///
321    /// # Example
322    ///
323    /// ```rust
324    /// use mini_moka::sync::Cache;
325    ///
326    /// let cache = Cache::new(10);
327    /// cache.insert('n', "Netherland Dwarf");
328    /// cache.insert('l', "Lop Eared");
329    /// cache.insert('d', "Dutch");
330    ///
331    /// // Ensure an entry exists.
332    /// assert!(cache.contains_key(&'n'));
333    ///
334    /// // However, followings may print stale number zeros instead of threes.
335    /// println!("{}", cache.entry_count());   // -> 0
336    /// println!("{}", cache.weighted_size()); // -> 0
337    ///
338    /// // To mitigate the inaccuracy, bring `ConcurrentCacheExt` trait to
339    /// // the scope so we can use `sync` method.
340    /// use mini_moka::sync::ConcurrentCacheExt;
341    /// // Call `sync` to run pending internal tasks.
342    /// cache.sync();
343    ///
344    /// // Followings will print the actual numbers.
345    /// println!("{}", cache.entry_count());   // -> 3
346    /// println!("{}", cache.weighted_size()); // -> 3
347    /// ```
348    ///
349    pub fn entry_count(&self) -> u64 {
350        self.base.entry_count()
351    }
352
353    /// Returns an approximate total weighted size of entries in this cache.
354    ///
355    /// The value returned is _an estimate_; the actual size may differ if there are
356    /// concurrent insertions or removals, or if some entries are pending removal due
357    /// to expiration. This inaccuracy can be mitigated by performing a `sync()`
358    /// first. See [`entry_count`](#method.entry_count) for a sample code.
359    pub fn weighted_size(&self) -> u64 {
360        self.base.weighted_size()
361    }
362}
363
364impl<K, V, S> Cache<K, V, S>
365where
366    K: Hash + Eq + Send + Sync + 'static,
367    V: Clone + Send + Sync + 'static,
368    S: BuildHasher + Clone + Send + Sync + 'static,
369{
370    pub(crate) fn with_everything(
371        max_capacity: Option<u64>,
372        initial_capacity: Option<usize>,
373        build_hasher: S,
374        weigher: Option<Weigher<K, V>>,
375        time_to_live: Option<Duration>,
376        time_to_idle: Option<Duration>,
377    ) -> Self {
378        Self {
379            base: BaseCache::new(
380                max_capacity,
381                initial_capacity,
382                build_hasher,
383                weigher,
384                time_to_live,
385                time_to_idle,
386            ),
387        }
388    }
389
390    /// Returns `true` if the cache contains a value for the key.
391    ///
392    /// Unlike the `get` method, this method is not considered a cache read operation,
393    /// so it does not update the historic popularity estimator or reset the idle
394    /// timer for the key.
395    ///
396    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
397    /// on the borrowed form _must_ match those for the key type.
398    pub fn contains_key<Q>(&self, key: &Q) -> bool
399    where
400        Arc<K>: Borrow<Q>,
401        Q: Hash + Eq + ?Sized,
402    {
403        self.base.contains_key(key)
404    }
405
406    /// Returns a _clone_ of the value corresponding to the key.
407    ///
408    /// If you want to store values that will be expensive to clone, wrap them by
409    /// `std::sync::Arc` before storing in a cache. [`Arc`][rustdoc-std-arc] is a
410    /// thread-safe reference-counted pointer and its `clone()` method is cheap.
411    ///
412    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
413    /// on the borrowed form _must_ match those for the key type.
414    ///
415    /// [rustdoc-std-arc]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html
416    pub fn get<Q>(&self, key: &Q) -> Option<V>
417    where
418        Arc<K>: Borrow<Q>,
419        Q: Hash + Eq + ?Sized,
420    {
421        self.base.get_with_hash(key, self.base.hash(key))
422    }
423
424    /// Deprecated, replaced with [`get`](#method.get)
425    #[doc(hidden)]
426    #[deprecated(since = "0.8.0", note = "Replaced with `get`")]
427    pub fn get_if_present<Q>(&self, key: &Q) -> Option<V>
428    where
429        Arc<K>: Borrow<Q>,
430        Q: Hash + Eq + ?Sized,
431    {
432        self.get(key)
433    }
434
435    /// Inserts a key-value pair into the cache.
436    ///
437    /// If the cache has this key present, the value is updated.
438    pub fn insert(&self, key: K, value: V) {
439        let hash = self.base.hash(&key);
440        let key = Arc::new(key);
441        self.insert_with_hash(key, hash, value)
442    }
443
444    pub(crate) fn insert_with_hash(&self, key: Arc<K>, hash: u64, value: V) {
445        let (op, now) = self.base.do_insert_with_hash(key, hash, value);
446        let hk = self.base.housekeeper.as_ref();
447        Self::schedule_write_op(
448            self.base.inner.as_ref(),
449            &self.base.write_op_ch,
450            op,
451            now,
452            hk,
453        )
454        .expect("Failed to insert");
455    }
456
457    /// Discards any cached value for the key.
458    ///
459    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
460    /// on the borrowed form _must_ match those for the key type.
461    pub fn invalidate<Q>(&self, key: &Q)
462    where
463        Arc<K>: Borrow<Q>,
464        Q: Hash + Eq + ?Sized,
465    {
466        if let Some(kv) = self.base.remove_entry(key) {
467            let op = WriteOp::Remove(kv);
468            let now = self.base.current_time_from_expiration_clock();
469            let hk = self.base.housekeeper.as_ref();
470            Self::schedule_write_op(
471                self.base.inner.as_ref(),
472                &self.base.write_op_ch,
473                op,
474                now,
475                hk,
476            )
477            .expect("Failed to remove");
478        }
479    }
480
481    /// Discards all cached values.
482    ///
483    /// This method returns immediately and a background thread will evict all the
484    /// cached values inserted before the time when this method was called. It is
485    /// guaranteed that the `get` method must not return these invalidated values
486    /// even if they have not been evicted.
487    ///
488    /// Like the `invalidate` method, this method does not clear the historic
489    /// popularity estimator of keys so that it retains the client activities of
490    /// trying to retrieve an item.
491    pub fn invalidate_all(&self) {
492        self.base.invalidate_all();
493    }
494}
495
496impl<'a, K, V, S> Cache<K, V, S>
497where
498    K: 'a + Eq + Hash,
499    V: 'a,
500    S: BuildHasher + Clone,
501{
502    /// Creates an iterator visiting all key-value pairs in arbitrary order. The
503    /// iterator element type is [`EntryRef<'a, K, V, S>`][moka-entry-ref].
504    ///
505    /// Unlike the `get` method, visiting entries via an iterator do not update the
506    /// historic popularity estimator or reset idle timers for keys.
507    ///
508    /// # Locking behavior
509    ///
510    /// This iterator relies on the iterator of [`dashmap::DashMap`][dashmap-iter],
511    /// which employs read-write locks. May deadlock if the thread holding an
512    /// iterator attempts to update the cache.
513    ///
514    /// [moka-entry-ref]: ./struct.EntryRef.html
515    /// [dashmap-iter]: <https://docs.rs/dashmap/*/dashmap/struct.DashMap.html#method.iter>
516    ///
517    /// # Examples
518    ///
519    /// ```rust
520    /// use mini_moka::sync::Cache;
521    ///
522    /// let cache = Cache::new(100);
523    /// cache.insert("Julia", 14);
524    ///
525    /// let mut iter = cache.iter();
526    /// let entry_ref = iter.next().unwrap();
527    /// assert_eq!(entry_ref.pair(), (&"Julia", &14));
528    /// assert_eq!(entry_ref.key(), &"Julia");
529    /// assert_eq!(entry_ref.value(), &14);
530    /// assert_eq!(*entry_ref, 14);
531    ///
532    /// assert!(iter.next().is_none());
533    /// ```
534    ///
535    pub fn iter(&self) -> Iter<'_, K, V, S> {
536        self.base.iter()
537    }
538}
539
540impl<K, V, S> ConcurrentCacheExt<K, V> for Cache<K, V, S>
541where
542    K: Hash + Eq + Send + Sync + 'static,
543    V: Send + Sync + 'static,
544    S: BuildHasher + Clone + Send + Sync + 'static,
545{
546    fn sync(&self) {
547        self.base.inner.sync(MAX_SYNC_REPEATS);
548    }
549}
550
551impl<'a, K, V, S> IntoIterator for &'a Cache<K, V, S>
552where
553    K: 'a + Eq + Hash,
554    V: 'a,
555    S: BuildHasher + Clone,
556{
557    type Item = EntryRef<'a, K, V, S>;
558
559    type IntoIter = Iter<'a, K, V, S>;
560
561    fn into_iter(self) -> Self::IntoIter {
562        self.iter()
563    }
564}
565
566// private methods
567impl<K, V, S> Cache<K, V, S>
568where
569    K: Hash + Eq + Send + Sync + 'static,
570    V: Clone + Send + Sync + 'static,
571    S: BuildHasher + Clone + Send + Sync + 'static,
572{
573    #[inline]
574    fn schedule_write_op(
575        inner: &impl InnerSync,
576        ch: &Sender<WriteOp<K, V>>,
577        op: WriteOp<K, V>,
578        now: Instant,
579        housekeeper: Option<&Arc<Housekeeper>>,
580    ) -> Result<(), TrySendError<WriteOp<K, V>>> {
581        let mut op = op;
582
583        // NOTES:
584        // - This will block when the channel is full.
585        // - We are doing a busy-loop here. We were originally calling `ch.send(op)?`,
586        //   but we got a notable performance degradation.
587        loop {
588            BaseCache::<K, V, S>::apply_reads_writes_if_needed(inner, ch, now, housekeeper);
589            match ch.try_send(op) {
590                Ok(()) => break,
591                Err(TrySendError::Full(op1)) => {
592                    op = op1;
593                    std::thread::sleep(Duration::from_micros(WRITE_RETRY_INTERVAL_MICROS));
594                }
595                Err(e @ TrySendError::Disconnected(_)) => return Err(e),
596            }
597        }
598        Ok(())
599    }
600}
601
602// For unit tests.
603#[cfg(test)]
604impl<K, V, S> Cache<K, V, S>
605where
606    K: Hash + Eq + Send + Sync + 'static,
607    V: Clone + Send + Sync + 'static,
608    S: BuildHasher + Clone + Send + Sync + 'static,
609{
610    pub(crate) fn is_table_empty(&self) -> bool {
611        self.entry_count() == 0
612    }
613
614    pub(crate) fn reconfigure_for_testing(&mut self) {
615        self.base.reconfigure_for_testing();
616    }
617
618    pub(crate) fn set_expiration_clock(&self, clock: Option<crate::common::time::Clock>) {
619        self.base.set_expiration_clock(clock);
620    }
621}
622
623// To see the debug prints, run test as `cargo test -- --nocapture`
624#[cfg(test)]
625mod tests {
626    use super::{Cache, ConcurrentCacheExt};
627    use crate::common::time::Clock;
628
629    use std::{sync::Arc, time::Duration};
630
631    #[test]
632    fn basic_single_thread() {
633        let mut cache = Cache::new(3);
634        cache.reconfigure_for_testing();
635
636        // Make the cache exterior immutable.
637        let cache = cache;
638
639        cache.insert("a", "alice");
640        cache.insert("b", "bob");
641        assert_eq!(cache.get(&"a"), Some("alice"));
642        assert!(cache.contains_key(&"a"));
643        assert!(cache.contains_key(&"b"));
644        assert_eq!(cache.get(&"b"), Some("bob"));
645        cache.sync();
646        // counts: a -> 1, b -> 1
647
648        cache.insert("c", "cindy");
649        assert_eq!(cache.get(&"c"), Some("cindy"));
650        assert!(cache.contains_key(&"c"));
651        // counts: a -> 1, b -> 1, c -> 1
652        cache.sync();
653
654        assert!(cache.contains_key(&"a"));
655        assert_eq!(cache.get(&"a"), Some("alice"));
656        assert_eq!(cache.get(&"b"), Some("bob"));
657        assert!(cache.contains_key(&"b"));
658        cache.sync();
659        // counts: a -> 2, b -> 2, c -> 1
660
661        // "d" should not be admitted because its frequency is too low.
662        cache.insert("d", "david"); //   count: d -> 0
663        cache.sync();
664        assert_eq!(cache.get(&"d"), None); //   d -> 1
665        assert!(!cache.contains_key(&"d"));
666
667        cache.insert("d", "david");
668        cache.sync();
669        assert!(!cache.contains_key(&"d"));
670        assert_eq!(cache.get(&"d"), None); //   d -> 2
671
672        // "d" should be admitted and "c" should be evicted
673        // because d's frequency is higher than c's.
674        cache.insert("d", "dennis");
675        cache.sync();
676        assert_eq!(cache.get(&"a"), Some("alice"));
677        assert_eq!(cache.get(&"b"), Some("bob"));
678        assert_eq!(cache.get(&"c"), None);
679        assert_eq!(cache.get(&"d"), Some("dennis"));
680        assert!(cache.contains_key(&"a"));
681        assert!(cache.contains_key(&"b"));
682        assert!(!cache.contains_key(&"c"));
683        assert!(cache.contains_key(&"d"));
684
685        cache.invalidate(&"b");
686        assert_eq!(cache.get(&"b"), None);
687        assert!(!cache.contains_key(&"b"));
688    }
689
690    #[test]
691    fn size_aware_eviction() {
692        let weigher = |_k: &&str, v: &(&str, u32)| v.1;
693
694        let alice = ("alice", 10);
695        let bob = ("bob", 15);
696        let bill = ("bill", 20);
697        let cindy = ("cindy", 5);
698        let david = ("david", 15);
699        let dennis = ("dennis", 15);
700
701        let mut cache = Cache::builder().max_capacity(31).weigher(weigher).build();
702        cache.reconfigure_for_testing();
703
704        // Make the cache exterior immutable.
705        let cache = cache;
706
707        cache.insert("a", alice);
708        cache.insert("b", bob);
709        assert_eq!(cache.get(&"a"), Some(alice));
710        assert!(cache.contains_key(&"a"));
711        assert!(cache.contains_key(&"b"));
712        assert_eq!(cache.get(&"b"), Some(bob));
713        cache.sync();
714        // order (LRU -> MRU) and counts: a -> 1, b -> 1
715
716        cache.insert("c", cindy);
717        assert_eq!(cache.get(&"c"), Some(cindy));
718        assert!(cache.contains_key(&"c"));
719        // order and counts: a -> 1, b -> 1, c -> 1
720        cache.sync();
721
722        assert!(cache.contains_key(&"a"));
723        assert_eq!(cache.get(&"a"), Some(alice));
724        assert_eq!(cache.get(&"b"), Some(bob));
725        assert!(cache.contains_key(&"b"));
726        cache.sync();
727        // order and counts: c -> 1, a -> 2, b -> 2
728
729        // To enter "d" (weight: 15), it needs to evict "c" (w: 5) and "a" (w: 10).
730        // "d" must have higher count than 3, which is the aggregated count
731        // of "a" and "c".
732        cache.insert("d", david); //   count: d -> 0
733        cache.sync();
734        assert_eq!(cache.get(&"d"), None); //   d -> 1
735        assert!(!cache.contains_key(&"d"));
736
737        cache.insert("d", david);
738        cache.sync();
739        assert!(!cache.contains_key(&"d"));
740        assert_eq!(cache.get(&"d"), None); //   d -> 2
741
742        cache.insert("d", david);
743        cache.sync();
744        assert_eq!(cache.get(&"d"), None); //   d -> 3
745        assert!(!cache.contains_key(&"d"));
746
747        cache.insert("d", david);
748        cache.sync();
749        assert!(!cache.contains_key(&"d"));
750        assert_eq!(cache.get(&"d"), None); //   d -> 4
751
752        // Finally "d" should be admitted by evicting "c" and "a".
753        cache.insert("d", dennis);
754        cache.sync();
755        assert_eq!(cache.get(&"a"), None);
756        assert_eq!(cache.get(&"b"), Some(bob));
757        assert_eq!(cache.get(&"c"), None);
758        assert_eq!(cache.get(&"d"), Some(dennis));
759        assert!(!cache.contains_key(&"a"));
760        assert!(cache.contains_key(&"b"));
761        assert!(!cache.contains_key(&"c"));
762        assert!(cache.contains_key(&"d"));
763
764        // Update "b" with "bill" (w: 15 -> 20). This should evict "d" (w: 15).
765        cache.insert("b", bill);
766        cache.sync();
767        assert_eq!(cache.get(&"b"), Some(bill));
768        assert_eq!(cache.get(&"d"), None);
769        assert!(cache.contains_key(&"b"));
770        assert!(!cache.contains_key(&"d"));
771
772        // Re-add "a" (w: 10) and update "b" with "bob" (w: 20 -> 15).
773        cache.insert("a", alice);
774        cache.insert("b", bob);
775        cache.sync();
776        assert_eq!(cache.get(&"a"), Some(alice));
777        assert_eq!(cache.get(&"b"), Some(bob));
778        assert_eq!(cache.get(&"d"), None);
779        assert!(cache.contains_key(&"a"));
780        assert!(cache.contains_key(&"b"));
781        assert!(!cache.contains_key(&"d"));
782
783        // Verify the sizes.
784        assert_eq!(cache.entry_count(), 2);
785        assert_eq!(cache.weighted_size(), 25);
786    }
787
788    #[test]
789    fn basic_multi_threads() {
790        let num_threads = 4;
791        let cache = Cache::new(100);
792
793        // https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
794        #[allow(clippy::needless_collect)]
795        let handles = (0..num_threads)
796            .map(|id| {
797                let cache = cache.clone();
798                std::thread::spawn(move || {
799                    cache.insert(10, format!("{}-100", id));
800                    cache.get(&10);
801                    cache.insert(20, format!("{}-200", id));
802                    cache.invalidate(&10);
803                })
804            })
805            .collect::<Vec<_>>();
806
807        handles.into_iter().for_each(|h| h.join().expect("Failed"));
808
809        assert!(cache.get(&10).is_none());
810        assert!(cache.get(&20).is_some());
811        assert!(!cache.contains_key(&10));
812        assert!(cache.contains_key(&20));
813    }
814
815    #[test]
816    fn invalidate_all() {
817        let mut cache = Cache::new(100);
818        cache.reconfigure_for_testing();
819
820        // Make the cache exterior immutable.
821        let cache = cache;
822
823        cache.insert("a", "alice");
824        cache.insert("b", "bob");
825        cache.insert("c", "cindy");
826        assert_eq!(cache.get(&"a"), Some("alice"));
827        assert_eq!(cache.get(&"b"), Some("bob"));
828        assert_eq!(cache.get(&"c"), Some("cindy"));
829        assert!(cache.contains_key(&"a"));
830        assert!(cache.contains_key(&"b"));
831        assert!(cache.contains_key(&"c"));
832
833        // `cache.sync()` is no longer needed here before invalidating. The last
834        // modified timestamp of the entries were updated when they were inserted.
835        // https://github.com/moka-rs/moka/issues/155
836
837        cache.invalidate_all();
838        cache.sync();
839
840        cache.insert("d", "david");
841        cache.sync();
842
843        assert!(cache.get(&"a").is_none());
844        assert!(cache.get(&"b").is_none());
845        assert!(cache.get(&"c").is_none());
846        assert_eq!(cache.get(&"d"), Some("david"));
847        assert!(!cache.contains_key(&"a"));
848        assert!(!cache.contains_key(&"b"));
849        assert!(!cache.contains_key(&"c"));
850        assert!(cache.contains_key(&"d"));
851    }
852
853    #[test]
854    fn time_to_live() {
855        let mut cache = Cache::builder()
856            .max_capacity(100)
857            .time_to_live(Duration::from_secs(10))
858            .build();
859
860        cache.reconfigure_for_testing();
861
862        let (clock, mock) = Clock::mock();
863        cache.set_expiration_clock(Some(clock));
864
865        // Make the cache exterior immutable.
866        let cache = cache;
867
868        cache.insert("a", "alice");
869        cache.sync();
870
871        mock.increment(Duration::from_secs(5)); // 5 secs from the start.
872        cache.sync();
873
874        assert_eq!(cache.get(&"a"), Some("alice"));
875        assert!(cache.contains_key(&"a"));
876
877        mock.increment(Duration::from_secs(5)); // 10 secs.
878        assert_eq!(cache.get(&"a"), None);
879        assert!(!cache.contains_key(&"a"));
880
881        assert_eq!(cache.iter().count(), 0);
882
883        cache.sync();
884        assert!(cache.is_table_empty());
885
886        cache.insert("b", "bob");
887        cache.sync();
888
889        assert_eq!(cache.entry_count(), 1);
890
891        mock.increment(Duration::from_secs(5)); // 15 secs.
892        cache.sync();
893
894        assert_eq!(cache.get(&"b"), Some("bob"));
895        assert!(cache.contains_key(&"b"));
896        assert_eq!(cache.entry_count(), 1);
897
898        cache.insert("b", "bill");
899        cache.sync();
900
901        mock.increment(Duration::from_secs(5)); // 20 secs
902        cache.sync();
903
904        assert_eq!(cache.get(&"b"), Some("bill"));
905        assert!(cache.contains_key(&"b"));
906        assert_eq!(cache.entry_count(), 1);
907
908        mock.increment(Duration::from_secs(5)); // 25 secs
909        assert_eq!(cache.get(&"a"), None);
910        assert_eq!(cache.get(&"b"), None);
911        assert!(!cache.contains_key(&"a"));
912        assert!(!cache.contains_key(&"b"));
913
914        assert_eq!(cache.iter().count(), 0);
915
916        cache.sync();
917        assert!(cache.is_table_empty());
918    }
919
920    #[test]
921    fn time_to_idle() {
922        let mut cache = Cache::builder()
923            .max_capacity(100)
924            .time_to_idle(Duration::from_secs(10))
925            .build();
926
927        cache.reconfigure_for_testing();
928
929        let (clock, mock) = Clock::mock();
930        cache.set_expiration_clock(Some(clock));
931
932        // Make the cache exterior immutable.
933        let cache = cache;
934
935        cache.insert("a", "alice");
936        cache.sync();
937
938        mock.increment(Duration::from_secs(5)); // 5 secs from the start.
939        cache.sync();
940
941        assert_eq!(cache.get(&"a"), Some("alice"));
942
943        mock.increment(Duration::from_secs(5)); // 10 secs.
944        cache.sync();
945
946        cache.insert("b", "bob");
947        cache.sync();
948
949        assert_eq!(cache.entry_count(), 2);
950
951        mock.increment(Duration::from_secs(2)); // 12 secs.
952        cache.sync();
953
954        // contains_key does not reset the idle timer for the key.
955        assert!(cache.contains_key(&"a"));
956        assert!(cache.contains_key(&"b"));
957        cache.sync();
958
959        assert_eq!(cache.entry_count(), 2);
960
961        mock.increment(Duration::from_secs(3)); // 15 secs.
962        assert_eq!(cache.get(&"a"), None);
963        assert_eq!(cache.get(&"b"), Some("bob"));
964        assert!(!cache.contains_key(&"a"));
965        assert!(cache.contains_key(&"b"));
966
967        assert_eq!(cache.iter().count(), 1);
968
969        cache.sync();
970        assert_eq!(cache.entry_count(), 1);
971
972        mock.increment(Duration::from_secs(10)); // 25 secs
973        assert_eq!(cache.get(&"a"), None);
974        assert_eq!(cache.get(&"b"), None);
975        assert!(!cache.contains_key(&"a"));
976        assert!(!cache.contains_key(&"b"));
977
978        assert_eq!(cache.iter().count(), 0);
979
980        cache.sync();
981        assert!(cache.is_table_empty());
982    }
983
984    #[test]
985    fn test_iter() {
986        const NUM_KEYS: usize = 50;
987
988        fn make_value(key: usize) -> String {
989            format!("val: {}", key)
990        }
991
992        let cache = Cache::builder()
993            .max_capacity(100)
994            .time_to_idle(Duration::from_secs(10))
995            .build();
996
997        for key in 0..NUM_KEYS {
998            cache.insert(key, make_value(key));
999        }
1000
1001        let mut key_set = std::collections::HashSet::new();
1002
1003        for entry in &cache {
1004            let (key, value) = entry.pair();
1005            assert_eq!(value, &make_value(*key));
1006
1007            key_set.insert(*key);
1008        }
1009
1010        // Ensure there are no missing or duplicate keys in the iteration.
1011        assert_eq!(key_set.len(), NUM_KEYS);
1012
1013        // DO NOT REMOVE THE COMMENT FROM THIS BLOCK.
1014        // This block demonstrates how you can write a code to get a deadlock.
1015        // {
1016        //     let mut iter = cache.iter();
1017        //     let _ = iter.next();
1018
1019        //     for key in 0..NUM_KEYS {
1020        //         cache.insert(key, make_value(key));
1021        //         println!("{}", key);
1022        //     }
1023
1024        //     let _ = iter.next();
1025        // }
1026    }
1027
1028    /// Runs 16 threads at the same time and ensures no deadlock occurs.
1029    ///
1030    /// - Eight of the threads will update key-values in the cache.
1031    /// - Eight others will iterate the cache.
1032    ///
1033    #[test]
1034    fn test_iter_multi_threads() {
1035        use std::collections::HashSet;
1036
1037        const NUM_KEYS: usize = 1024;
1038        const NUM_THREADS: usize = 16;
1039
1040        fn make_value(key: usize) -> String {
1041            format!("val: {}", key)
1042        }
1043
1044        let cache = Cache::builder()
1045            .max_capacity(2048)
1046            .time_to_idle(Duration::from_secs(10))
1047            .build();
1048
1049        // Initialize the cache.
1050        for key in 0..NUM_KEYS {
1051            cache.insert(key, make_value(key));
1052        }
1053
1054        let rw_lock = Arc::new(std::sync::RwLock::<()>::default());
1055        let write_lock = rw_lock.write().unwrap();
1056
1057        // https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
1058        #[allow(clippy::needless_collect)]
1059        let handles = (0..NUM_THREADS)
1060            .map(|n| {
1061                let cache = cache.clone();
1062                let rw_lock = Arc::clone(&rw_lock);
1063
1064                if n % 2 == 0 {
1065                    // This thread will update the cache.
1066                    std::thread::spawn(move || {
1067                        let read_lock = rw_lock.read().unwrap();
1068                        for key in 0..NUM_KEYS {
1069                            // TODO: Update keys in a random order?
1070                            cache.insert(key, make_value(key));
1071                        }
1072                        std::mem::drop(read_lock);
1073                    })
1074                } else {
1075                    // This thread will iterate the cache.
1076                    std::thread::spawn(move || {
1077                        let read_lock = rw_lock.read().unwrap();
1078                        let mut key_set = HashSet::new();
1079                        for entry in &cache {
1080                            let (key, value) = entry.pair();
1081                            assert_eq!(value, &make_value(*key));
1082                            key_set.insert(*key);
1083                        }
1084                        // Ensure there are no missing or duplicate keys in the iteration.
1085                        assert_eq!(key_set.len(), NUM_KEYS);
1086                        std::mem::drop(read_lock);
1087                    })
1088                }
1089            })
1090            .collect::<Vec<_>>();
1091
1092        // Let these threads to run by releasing the write lock.
1093        std::mem::drop(write_lock);
1094
1095        handles.into_iter().for_each(|h| h.join().expect("Failed"));
1096
1097        // Ensure there are no missing or duplicate keys in the iteration.
1098        let key_set = cache.iter().map(|ent| *ent.key()).collect::<HashSet<_>>();
1099        assert_eq!(key_set.len(), NUM_KEYS);
1100    }
1101
1102    #[test]
1103    fn test_debug_format() {
1104        let cache = Cache::new(10);
1105        cache.insert('a', "alice");
1106        cache.insert('b', "bob");
1107        cache.insert('c', "cindy");
1108
1109        let debug_str = format!("{:?}", cache);
1110        assert!(debug_str.starts_with('{'));
1111        assert!(debug_str.contains(r#"'a': "alice""#));
1112        assert!(debug_str.contains(r#"'b': "bob""#));
1113        assert!(debug_str.contains(r#"'c': "cindy""#));
1114        assert!(debug_str.ends_with('}'));
1115    }
1116}