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}