mini_moka/unsync/
cache.rs

1use super::{
2    deques::Deques, AccessTime, CacheBuilder, Iter, KeyDate, KeyHashDate, ValueEntry, Weigher,
3};
4use crate::{
5    common::{
6        self,
7        deque::{DeqNode, Deque},
8        frequency_sketch::FrequencySketch,
9        time::{CheckedTimeOps, Clock, Instant},
10        CacheRegion,
11    },
12    Policy,
13};
14
15use smallvec::SmallVec;
16use std::{
17    borrow::Borrow,
18    collections::{hash_map::RandomState, HashMap},
19    fmt,
20    hash::{BuildHasher, Hash, Hasher},
21    ptr::NonNull,
22    rc::Rc,
23    time::Duration,
24};
25
26const EVICTION_BATCH_SIZE: usize = 100;
27
28type CacheStore<K, V, S> = std::collections::HashMap<Rc<K>, ValueEntry<K, V>, S>;
29
30/// An in-memory cache that is _not_ thread-safe.
31///
32/// `Cache` utilizes a hash table [`std::collections::HashMap`][std-hashmap] from the
33/// standard library for the central key-value storage. `Cache` performs a
34/// best-effort bounding of the map using an entry replacement algorithm to determine
35/// which entries to evict when the capacity is exceeded.
36///
37/// [std-hashmap]: https://doc.rust-lang.org/std/collections/struct.HashMap.html
38///
39/// # Characteristic difference between `unsync` and `sync`/`future` caches
40///
41/// If you use a cache from a single thread application, `unsync::Cache` may
42/// outperform other caches for updates and retrievals because other caches have some
43/// overhead on syncing internal data structures between threads.
44///
45/// However, other caches may outperform `unsync::Cache` on the same operations when
46/// expiration polices are configured on a multi-core system. `unsync::Cache` evicts
47/// expired entries as a part of update and retrieval operations while others evict
48/// them using a dedicated background thread.
49///
50/// # Examples
51///
52/// Cache entries are manually added using the insert method, and are stored in the
53/// cache until either evicted or manually invalidated.
54///
55/// Here's an example of reading and updating a cache by using the main thread:
56///
57///```rust
58/// use mini_moka::unsync::Cache;
59///
60/// const NUM_KEYS: usize = 64;
61///
62/// fn value(n: usize) -> String {
63///     format!("value {}", n)
64/// }
65///
66/// // Create a cache that can store up to 10,000 entries.
67/// let mut cache = Cache::new(10_000);
68///
69/// // Insert 64 entries.
70/// for key in 0..NUM_KEYS {
71///     cache.insert(key, value(key));
72/// }
73///
74/// // Invalidate every 4 element of the inserted entries.
75/// for key in (0..NUM_KEYS).step_by(4) {
76///     cache.invalidate(&key);
77/// }
78///
79/// // Verify the result.
80/// for key in 0..NUM_KEYS {
81///     if key % 4 == 0 {
82///         assert_eq!(cache.get(&key), None);
83///     } else {
84///         assert_eq!(cache.get(&key), Some(&value(key)));
85///     }
86/// }
87/// ```
88///
89/// # Size-based Eviction
90///
91/// ```rust
92/// use std::convert::TryInto;
93/// use mini_moka::unsync::Cache;
94///
95/// // Evict based on the number of entries in the cache.
96/// let mut cache = Cache::builder()
97///     // Up to 10,000 entries.
98///     .max_capacity(10_000)
99///     // Create the cache.
100///     .build();
101/// cache.insert(1, "one".to_string());
102///
103/// // Evict based on the byte length of strings in the cache.
104/// let mut cache = Cache::builder()
105///     // A weigher closure takes &K and &V and returns a u32
106///     // representing the relative size of the entry.
107///     .weigher(|_key, value: &String| -> u32 {
108///         value.len().try_into().unwrap_or(u32::MAX)
109///     })
110///     // This cache will hold up to 32MiB of values.
111///     .max_capacity(32 * 1024 * 1024)
112///     .build();
113/// cache.insert(2, "two".to_string());
114/// ```
115///
116/// If your cache should not grow beyond a certain size, use the `max_capacity`
117/// method of the [`CacheBuilder`][builder-struct] to set the upper bound. The cache
118/// will try to evict entries that have not been used recently or very often.
119///
120/// At the cache creation time, a weigher closure can be set by the `weigher` method
121/// of the `CacheBuilder`. A weigher closure takes `&K` and `&V` as the arguments and
122/// returns a `u32` representing the relative size of the entry:
123///
124/// - If the `weigher` is _not_ set, the cache will treat each entry has the same
125///   size of `1`. This means the cache will be bounded by the number of entries.
126/// - If the `weigher` is set, the cache will call the weigher to calculate the
127///   weighted size (relative size) on an entry. This means the cache will be bounded
128///   by the total weighted size of entries.
129///
130/// Note that weighted sizes are not used when making eviction selections.
131///
132/// [builder-struct]: ./struct.CacheBuilder.html
133///
134/// # Time-based Expirations
135///
136/// `Cache` supports the following expiration policies:
137///
138/// - **Time to live**: A cached entry will be expired after the specified duration
139///   past from `insert`.
140/// - **Time to idle**: A cached entry will be expired after the specified duration
141///   past from `get` or `insert`.
142///
143/// See the [`CacheBuilder`][builder-struct]'s doc for how to configure a cache
144/// with them.
145///
146/// [builder-struct]: ./struct.CacheBuilder.html
147///
148/// # Hashing Algorithm
149///
150/// By default, `Cache` uses a hashing algorithm selected to provide resistance
151/// against HashDoS attacks. It will the same one used by
152/// `std::collections::HashMap`, which is currently SipHash 1-3.
153///
154/// While SipHash's performance is very competitive for medium sized keys, other
155/// hashing algorithms will outperform it for small keys such as integers as well as
156/// large keys such as long strings. However those algorithms will typically not
157/// protect against attacks such as HashDoS.
158///
159/// The hashing algorithm can be replaced on a per-`Cache` basis using the
160/// [`build_with_hasher`][build-with-hasher-method] method of the
161/// `CacheBuilder`. Many alternative algorithms are available on crates.io, such
162/// as the [aHash][ahash-crate] crate.
163///
164/// [build-with-hasher-method]: ./struct.CacheBuilder.html#method.build_with_hasher
165/// [ahash-crate]: https://crates.io/crates/ahash
166///
167pub struct Cache<K, V, S = RandomState> {
168    max_capacity: Option<u64>,
169    entry_count: u64,
170    weighted_size: u64,
171    cache: CacheStore<K, V, S>,
172    build_hasher: S,
173    weigher: Option<Weigher<K, V>>,
174    deques: Deques<K>,
175    frequency_sketch: FrequencySketch,
176    frequency_sketch_enabled: bool,
177    time_to_live: Option<Duration>,
178    time_to_idle: Option<Duration>,
179    expiration_clock: Option<Clock>,
180}
181
182impl<K, V, S> fmt::Debug for Cache<K, V, S>
183where
184    K: fmt::Debug + Eq + Hash,
185    V: fmt::Debug,
186    // TODO: Remove these bounds from S.
187    S: BuildHasher + Clone,
188{
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        let mut d_map = f.debug_map();
191
192        for (k, v) in self.iter() {
193            d_map.entry(&k, &v);
194        }
195
196        d_map.finish()
197    }
198}
199
200impl<K, V> Cache<K, V, RandomState>
201where
202    K: Hash + Eq,
203{
204    /// Constructs a new `Cache<K, V>` that will store up to the `max_capacity` entries.
205    ///
206    /// To adjust various configuration knobs such as `initial_capacity` or
207    /// `time_to_live`, use the [`CacheBuilder`][builder-struct].
208    ///
209    /// [builder-struct]: ./struct.CacheBuilder.html
210    pub fn new(max_capacity: u64) -> Self {
211        let build_hasher = RandomState::default();
212        Self::with_everything(Some(max_capacity), None, build_hasher, None, None, None)
213    }
214
215    /// Returns a [`CacheBuilder`][builder-struct], which can builds a `Cache` with
216    /// various configuration knobs.
217    ///
218    /// [builder-struct]: ./struct.CacheBuilder.html
219    pub fn builder() -> CacheBuilder<K, V, Cache<K, V, RandomState>> {
220        CacheBuilder::default()
221    }
222}
223
224//
225// public
226//
227impl<K, V, S> Cache<K, V, S> {
228    /// Returns a read-only cache policy of this cache.
229    ///
230    /// At this time, cache policy cannot be modified after cache creation.
231    /// A future version may support to modify it.
232    pub fn policy(&self) -> Policy {
233        Policy::new(self.max_capacity, self.time_to_live, self.time_to_idle)
234    }
235
236    /// Returns the number of entries in this cache.
237    ///
238    /// # Example
239    ///
240    /// ```rust
241    /// use mini_moka::unsync::Cache;
242    ///
243    /// let mut cache = Cache::new(10);
244    /// cache.insert('n', "Netherland Dwarf");
245    /// cache.insert('l', "Lop Eared");
246    /// cache.insert('d', "Dutch");
247    ///
248    /// // Ensure an entry exists.
249    /// assert!(cache.contains_key(&'n'));
250    ///
251    /// // Followings will print the actual numbers.
252    /// println!("{}", cache.entry_count());   // -> 3
253    /// println!("{}", cache.weighted_size()); // -> 3
254    /// ```
255    ///
256    pub fn entry_count(&self) -> u64 {
257        self.entry_count
258    }
259
260    /// Returns the total weighted size of entries in this cache.
261    ///
262    /// See [`entry_count`](#method.entry_count) for a sample code.
263    pub fn weighted_size(&self) -> u64 {
264        self.weighted_size
265    }
266}
267
268impl<K, V, S> Cache<K, V, S>
269where
270    K: Hash + Eq,
271    S: BuildHasher + Clone,
272{
273    pub(crate) fn with_everything(
274        max_capacity: Option<u64>,
275        initial_capacity: Option<usize>,
276        build_hasher: S,
277        weigher: Option<Weigher<K, V>>,
278        time_to_live: Option<Duration>,
279        time_to_idle: Option<Duration>,
280    ) -> Self {
281        let cache = HashMap::with_capacity_and_hasher(
282            initial_capacity.unwrap_or_default(),
283            build_hasher.clone(),
284        );
285
286        Self {
287            max_capacity,
288            entry_count: 0,
289            weighted_size: 0,
290            cache,
291            build_hasher,
292            weigher,
293            deques: Default::default(),
294            frequency_sketch: Default::default(),
295            frequency_sketch_enabled: false,
296            time_to_live,
297            time_to_idle,
298            expiration_clock: None,
299        }
300    }
301
302    /// Returns `true` if the cache contains a value for the key.
303    ///
304    /// Unlike the `get` method, this method is not considered a cache read operation,
305    /// so it does not update the historic popularity estimator or reset the idle
306    /// timer for the key.
307    ///
308    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
309    /// on the borrowed form _must_ match those for the key type.
310    pub fn contains_key<Q>(&mut self, key: &Q) -> bool
311    where
312        Rc<K>: Borrow<Q>,
313        Q: Hash + Eq + ?Sized,
314    {
315        let timestamp = self.evict_expired_if_needed();
316        self.evict_lru_entries();
317
318        match (self.cache.get(key), timestamp) {
319            // Value not found.
320            (None, _) => false,
321            // Value found, no expiry.
322            (Some(_), None) => true,
323            // Value found, check if expired.
324            (Some(entry), Some(ts)) => {
325                !Self::is_expired_entry_wo(&self.time_to_live, entry, ts)
326                    && !Self::is_expired_entry_ao(&self.time_to_idle, entry, ts)
327            }
328        }
329    }
330
331    /// Returns an immutable reference of the value corresponding to the key.
332    ///
333    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
334    /// on the borrowed form _must_ match those for the key type.
335    pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
336    where
337        Rc<K>: Borrow<Q>,
338        Q: Hash + Eq + ?Sized,
339    {
340        let timestamp = self.evict_expired_if_needed();
341        self.evict_lru_entries();
342        self.frequency_sketch.increment(self.hash(key));
343
344        match (self.cache.get_mut(key), timestamp, &mut self.deques) {
345            // Value not found.
346            (None, _, _) => None,
347            // Value found, no expiry.
348            (Some(entry), None, deqs) => {
349                Self::record_hit(deqs, entry, None);
350                Some(&entry.value)
351            }
352            // Value found, check if expired.
353            (Some(entry), Some(ts), deqs) => {
354                if Self::is_expired_entry_wo(&self.time_to_live, entry, ts)
355                    || Self::is_expired_entry_ao(&self.time_to_idle, entry, ts)
356                {
357                    None
358                } else {
359                    Self::record_hit(deqs, entry, timestamp);
360                    Some(&entry.value)
361                }
362            }
363        }
364    }
365
366    pub(crate) fn is_expired_entry(&self, entry: &ValueEntry<K, V>) -> bool {
367        let now = self.current_time_from_expiration_clock();
368        Self::is_expired_entry_wo(&self.time_to_live, entry, now)
369            || Self::is_expired_entry_ao(&self.time_to_idle, entry, now)
370    }
371
372    /// Inserts a key-value pair into the cache.
373    ///
374    /// If the cache has this key present, the value is updated.
375    pub fn insert(&mut self, key: K, value: V) {
376        let timestamp = self.evict_expired_if_needed();
377        self.evict_lru_entries();
378        let policy_weight = weigh(&mut self.weigher, &key, &value);
379        let key = Rc::new(key);
380        let entry = ValueEntry::new(value, policy_weight);
381
382        if let Some(old_entry) = self.cache.insert(Rc::clone(&key), entry) {
383            self.handle_update(key, timestamp, policy_weight, old_entry);
384        } else {
385            let hash = self.hash(&key);
386            self.handle_insert(key, hash, policy_weight, timestamp);
387        }
388    }
389
390    /// Discards any cached value for the key.
391    ///
392    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
393    /// on the borrowed form _must_ match those for the key type.
394    pub fn invalidate<Q>(&mut self, key: &Q)
395    where
396        Rc<K>: Borrow<Q>,
397        Q: Hash + Eq + ?Sized,
398    {
399        self.evict_expired_if_needed();
400        self.evict_lru_entries();
401
402        if let Some(mut entry) = self.cache.remove(key) {
403            let weight = entry.policy_weight();
404            self.deques.unlink_ao(&mut entry);
405            Deques::unlink_wo(&mut self.deques.write_order, &mut entry);
406            self.saturating_sub_from_total_weight(weight as u64);
407        }
408    }
409
410    /// Discards all cached values.
411    ///
412    /// Like the `invalidate` method, this method does not clear the historic
413    /// popularity estimator of keys so that it retains the client activities of
414    /// trying to retrieve an item.
415    pub fn invalidate_all(&mut self) {
416        self.cache.clear();
417        self.deques.clear();
418        self.weighted_size = 0;
419    }
420
421    /// Discards cached values that satisfy a predicate.
422    ///
423    /// `invalidate_entries_if` takes a closure that returns `true` or `false`.
424    /// `invalidate_entries_if` will apply the closure to each cached value,
425    /// and if the closure returns `true`, the value will be invalidated.
426    ///
427    /// Like the `invalidate` method, this method does not clear the historic
428    /// popularity estimator of keys so that it retains the client activities of
429    /// trying to retrieve an item.
430
431    // We need this #[allow(...)] to avoid a false Clippy warning about needless
432    // collect to create keys_to_invalidate.
433    // clippy 0.1.52 (9a1dfd2dc5c 2021-04-30) in Rust 1.52.0-beta.7
434    #[allow(clippy::needless_collect)]
435    pub fn invalidate_entries_if(&mut self, mut predicate: impl FnMut(&K, &V) -> bool) {
436        let Self { cache, deques, .. } = self;
437
438        // Since we can't do cache.iter() and cache.remove() at the same time,
439        // invalidation needs to run in two steps:
440        // 1. Examine all entries in this cache and collect keys to invalidate.
441        // 2. Remove entries for the keys.
442
443        let keys_to_invalidate = cache
444            .iter()
445            .filter(|(key, entry)| (predicate)(key, &entry.value))
446            .map(|(key, _)| Rc::clone(key))
447            .collect::<Vec<_>>();
448
449        let mut invalidated = 0u64;
450
451        keys_to_invalidate.into_iter().for_each(|k| {
452            if let Some(mut entry) = cache.remove(&k) {
453                let weight = entry.policy_weight();
454                deques.unlink_ao(&mut entry);
455                Deques::unlink_wo(&mut deques.write_order, &mut entry);
456                invalidated = invalidated.saturating_sub(weight as u64);
457            }
458        });
459        self.saturating_sub_from_total_weight(invalidated);
460    }
461
462    /// Creates an iterator visiting all key-value pairs in arbitrary order. The
463    /// iterator element type is `(&K, &V)`.
464    ///
465    /// Unlike the `get` method, visiting entries via an iterator do not update the
466    /// historic popularity estimator or reset idle timers for keys.
467    ///
468    /// # Examples
469    ///
470    /// ```rust
471    /// use mini_moka::unsync::Cache;
472    ///
473    /// let mut cache = Cache::new(100);
474    /// cache.insert("Julia", 14);
475    ///
476    /// let mut iter = cache.iter();
477    /// let (k, v) = iter.next().unwrap(); // (&K, &V)
478    /// assert_eq!(k, &"Julia");
479    /// assert_eq!(v, &14);
480    ///
481    /// assert!(iter.next().is_none());
482    /// ```
483    ///
484    pub fn iter(&self) -> Iter<'_, K, V, S> {
485        Iter::new(self, self.cache.iter())
486    }
487}
488
489//
490// private
491//
492impl<K, V, S> Cache<K, V, S>
493where
494    K: Hash + Eq,
495    S: BuildHasher + Clone,
496{
497    #[inline]
498    fn hash<Q>(&self, key: &Q) -> u64
499    where
500        Rc<K>: Borrow<Q>,
501        Q: Hash + Eq + ?Sized,
502    {
503        let mut hasher = self.build_hasher.build_hasher();
504        key.hash(&mut hasher);
505        hasher.finish()
506    }
507
508    #[inline]
509    fn has_expiry(&self) -> bool {
510        self.time_to_live.is_some() || self.time_to_idle.is_some()
511    }
512
513    #[inline]
514    fn evict_expired_if_needed(&mut self) -> Option<Instant> {
515        if self.has_expiry() {
516            let ts = self.current_time_from_expiration_clock();
517            self.evict_expired(ts);
518            Some(ts)
519        } else {
520            None
521        }
522    }
523
524    #[inline]
525    fn current_time_from_expiration_clock(&self) -> Instant {
526        if let Some(clock) = &self.expiration_clock {
527            Instant::new(clock.now())
528        } else {
529            Instant::now()
530        }
531    }
532
533    #[inline]
534    fn is_expired_entry_ao(
535        time_to_idle: &Option<Duration>,
536        entry: &impl AccessTime,
537        now: Instant,
538    ) -> bool {
539        if let (Some(ts), Some(tti)) = (entry.last_accessed(), time_to_idle) {
540            let checked_add = ts.checked_add(*tti);
541            if checked_add.is_none() {
542                panic!("ttl overflow")
543            }
544            return checked_add.unwrap() <= now;
545        }
546        false
547    }
548
549    #[inline]
550    fn is_expired_entry_wo(
551        time_to_live: &Option<Duration>,
552        entry: &impl AccessTime,
553        now: Instant,
554    ) -> bool {
555        if let (Some(ts), Some(ttl)) = (entry.last_modified(), time_to_live) {
556            let checked_add = ts.checked_add(*ttl);
557            if checked_add.is_none() {
558                panic!("ttl overflow")
559            }
560            return checked_add.unwrap() <= now;
561        }
562        false
563    }
564
565    fn record_hit(deques: &mut Deques<K>, entry: &mut ValueEntry<K, V>, ts: Option<Instant>) {
566        if let Some(ts) = ts {
567            entry.set_last_accessed(ts);
568        }
569        deques.move_to_back_ao(entry)
570    }
571
572    fn has_enough_capacity(&self, candidate_weight: u32, ws: u64) -> bool {
573        self.max_capacity
574            .map(|limit| ws + candidate_weight as u64 <= limit)
575            .unwrap_or(true)
576    }
577
578    fn weights_to_evict(&self) -> u64 {
579        self.max_capacity
580            .map(|limit| self.weighted_size.saturating_sub(limit))
581            .unwrap_or_default()
582    }
583
584    #[inline]
585    fn should_enable_frequency_sketch(&self) -> bool {
586        if self.frequency_sketch_enabled {
587            false
588        } else if let Some(max_cap) = self.max_capacity {
589            self.weighted_size >= max_cap / 2
590        } else {
591            false
592        }
593    }
594
595    #[inline]
596    fn enable_frequency_sketch(&mut self) {
597        if let Some(max_cap) = self.max_capacity {
598            let cap = if self.weigher.is_none() {
599                max_cap
600            } else {
601                (self.entry_count as f64 * (self.weighted_size as f64 / max_cap as f64)) as u64
602            };
603            self.do_enable_frequency_sketch(cap);
604        }
605    }
606
607    #[cfg(test)]
608    fn enable_frequency_sketch_for_testing(&mut self) {
609        if let Some(max_cap) = self.max_capacity {
610            self.do_enable_frequency_sketch(max_cap);
611        }
612    }
613
614    #[inline]
615    fn do_enable_frequency_sketch(&mut self, cache_capacity: u64) {
616        let skt_capacity = common::sketch_capacity(cache_capacity);
617        self.frequency_sketch.ensure_capacity(skt_capacity);
618        self.frequency_sketch_enabled = true;
619    }
620
621    fn saturating_add_to_total_weight(&mut self, weight: u64) {
622        let total = &mut self.weighted_size;
623        *total = total.saturating_add(weight);
624    }
625
626    fn saturating_sub_from_total_weight(&mut self, weight: u64) {
627        let total = &mut self.weighted_size;
628        *total = total.saturating_sub(weight);
629    }
630
631    #[inline]
632    fn handle_insert(
633        &mut self,
634        key: Rc<K>,
635        hash: u64,
636        policy_weight: u32,
637        timestamp: Option<Instant>,
638    ) {
639        let has_free_space = self.has_enough_capacity(policy_weight, self.weighted_size);
640        let (cache, deqs, freq) = (&mut self.cache, &mut self.deques, &self.frequency_sketch);
641
642        if has_free_space {
643            // Add the candidate to the deque.
644            let key = Rc::clone(&key);
645            let entry = cache.get_mut(&key).unwrap();
646            deqs.push_back_ao(
647                CacheRegion::MainProbation,
648                KeyHashDate::new(Rc::clone(&key), hash, timestamp),
649                entry,
650            );
651            if self.time_to_live.is_some() {
652                deqs.push_back_wo(KeyDate::new(key, timestamp), entry);
653            }
654            self.entry_count += 1;
655            self.saturating_add_to_total_weight(policy_weight as u64);
656
657            if self.should_enable_frequency_sketch() {
658                self.enable_frequency_sketch();
659            }
660
661            return;
662        }
663
664        if let Some(max) = self.max_capacity {
665            if policy_weight as u64 > max {
666                // The candidate is too big to fit in the cache. Reject it.
667                cache.remove(&Rc::clone(&key));
668                return;
669            }
670        }
671
672        let mut candidate = EntrySizeAndFrequency::new(policy_weight as u64);
673        candidate.add_frequency(freq, hash);
674
675        match Self::admit(&candidate, cache, deqs, freq, &mut self.weigher) {
676            AdmissionResult::Admitted {
677                victim_nodes,
678                victims_weight,
679            } => {
680                // Remove the victims from the cache (hash map) and deque.
681                for victim in victim_nodes {
682                    // Remove the victim from the hash map.
683                    let mut vic_entry = cache
684                        .remove(unsafe { &victim.as_ref().element.key })
685                        .expect("Cannot remove a victim from the hash map");
686                    // And then remove the victim from the deques.
687                    deqs.unlink_ao(&mut vic_entry);
688                    Deques::unlink_wo(&mut deqs.write_order, &mut vic_entry);
689                    self.entry_count -= 1;
690                }
691
692                // Add the candidate to the deque.
693                let entry = cache.get_mut(&key).unwrap();
694                let key = Rc::clone(&key);
695                deqs.push_back_ao(
696                    CacheRegion::MainProbation,
697                    KeyHashDate::new(Rc::clone(&key), hash, timestamp),
698                    entry,
699                );
700                if self.time_to_live.is_some() {
701                    deqs.push_back_wo(KeyDate::new(key, timestamp), entry);
702                }
703
704                self.entry_count += 1;
705                Self::saturating_sub_from_total_weight(self, victims_weight);
706                Self::saturating_add_to_total_weight(self, policy_weight as u64);
707
708                if self.should_enable_frequency_sketch() {
709                    self.enable_frequency_sketch();
710                }
711            }
712            AdmissionResult::Rejected => {
713                // Remove the candidate from the cache.
714                cache.remove(&key);
715            }
716        }
717    }
718
719    /// Performs size-aware admission explained in the paper:
720    /// [Lightweight Robust Size Aware Cache Management][size-aware-cache-paper]
721    /// by Gil Einziger, Ohad Eytan, Roy Friedman, Ben Manes.
722    ///
723    /// [size-aware-cache-paper]: https://arxiv.org/abs/2105.08770
724    ///
725    /// There are some modifications in this implementation:
726    /// - To admit to the main space, candidate's frequency must be higher than
727    ///   the aggregated frequencies of the potential victims. (In the paper,
728    ///   `>=` operator is used rather than `>`)  The `>` operator will do a better
729    ///   job to prevent the main space from polluting.
730    /// - When a candidate is rejected, the potential victims will stay at the LRU
731    ///   position of the probation access-order queue. (In the paper, they will be
732    ///   promoted (to the MRU position?) to force the eviction policy to select a
733    ///   different set of victims for the next candidate). We may implement the
734    ///   paper's behavior later?
735    ///
736    #[inline]
737    fn admit(
738        candidate: &EntrySizeAndFrequency,
739        cache: &CacheStore<K, V, S>,
740        deqs: &Deques<K>,
741        freq: &FrequencySketch,
742        weigher: &mut Option<Weigher<K, V>>,
743    ) -> AdmissionResult<K> {
744        let mut victims = EntrySizeAndFrequency::default();
745        let mut victim_nodes = SmallVec::default();
746
747        // Get first potential victim at the LRU position.
748        let mut next_victim = deqs.probation.peek_front_ptr();
749
750        // Aggregate potential victims.
751        while victims.weight < candidate.weight {
752            if candidate.freq < victims.freq {
753                break;
754            }
755            if let Some(victim) = next_victim.take() {
756                next_victim = DeqNode::next_node_ptr(victim);
757                let vic_elem = &unsafe { victim.as_ref() }.element;
758
759                let vic_entry = cache
760                    .get(&vic_elem.key)
761                    .expect("Cannot get an victim entry");
762                victims.add_policy_weight(vic_elem.key.as_ref(), &vic_entry.value, weigher);
763                victims.add_frequency(freq, vic_elem.hash);
764                victim_nodes.push(victim);
765            } else {
766                // No more potential victims.
767                break;
768            }
769        }
770
771        // Admit or reject the candidate.
772
773        // TODO: Implement some randomness to mitigate hash DoS attack.
774        // See Caffeine's implementation.
775
776        if victims.weight >= candidate.weight && candidate.freq > victims.freq {
777            AdmissionResult::Admitted {
778                victim_nodes,
779                victims_weight: victims.weight,
780            }
781        } else {
782            AdmissionResult::Rejected
783        }
784    }
785
786    fn handle_update(
787        &mut self,
788        key: Rc<K>,
789        timestamp: Option<Instant>,
790        policy_weight: u32,
791        old_entry: ValueEntry<K, V>,
792    ) {
793        let old_policy_weight = old_entry.policy_weight();
794
795        let entry = self.cache.get_mut(&key).unwrap();
796        entry.replace_deq_nodes_with(old_entry);
797        if let Some(ts) = timestamp {
798            entry.set_last_accessed(ts);
799            entry.set_last_modified(ts);
800        }
801        entry.set_policy_weight(policy_weight);
802
803        let deqs = &mut self.deques;
804        deqs.move_to_back_ao(entry);
805        if self.time_to_live.is_some() {
806            deqs.move_to_back_wo(entry);
807        }
808
809        self.saturating_sub_from_total_weight(old_policy_weight as u64);
810        self.saturating_add_to_total_weight(policy_weight as u64);
811    }
812
813    fn evict_expired(&mut self, now: Instant) {
814        if self.time_to_live.is_some() {
815            let (count, weight) = self.remove_expired_wo(EVICTION_BATCH_SIZE, now);
816            self.entry_count -= count;
817            self.saturating_sub_from_total_weight(weight);
818        }
819
820        if self.time_to_idle.is_some() {
821            let deqs = &mut self.deques;
822            let (window, probation, protected, wo, cache, time_to_idle) = (
823                &mut deqs.window,
824                &mut deqs.probation,
825                &mut deqs.protected,
826                &mut deqs.write_order,
827                &mut self.cache,
828                &self.time_to_idle,
829            );
830
831            let mut rm_expired_ao = |name, deq| {
832                Self::remove_expired_ao(
833                    name,
834                    deq,
835                    wo,
836                    cache,
837                    time_to_idle,
838                    EVICTION_BATCH_SIZE,
839                    now,
840                )
841            };
842
843            let (count1, weight1) = rm_expired_ao("window", window);
844            let (count2, weight2) = rm_expired_ao("probation", probation);
845            let (count3, weight3) = rm_expired_ao("protected", protected);
846
847            self.entry_count -= count1 + count2 + count3;
848            self.saturating_sub_from_total_weight(weight1);
849            self.saturating_sub_from_total_weight(weight2);
850            self.saturating_sub_from_total_weight(weight3);
851        }
852    }
853
854    // Returns (u64, u64) where (evicted_entry_count, evicted_policy_weight).
855    #[inline]
856    fn remove_expired_ao(
857        deq_name: &str,
858        deq: &mut Deque<KeyHashDate<K>>,
859        write_order_deq: &mut Deque<KeyDate<K>>,
860        cache: &mut CacheStore<K, V, S>,
861        time_to_idle: &Option<Duration>,
862        batch_size: usize,
863        now: Instant,
864    ) -> (u64, u64) {
865        let mut evicted_entry_count = 0u64;
866        let mut evicted_policy_weight = 0u64;
867
868        for _ in 0..batch_size {
869            let key = deq
870                .peek_front()
871                .and_then(|node| {
872                    if Self::is_expired_entry_ao(time_to_idle, node, now) {
873                        Some(Some(Rc::clone(&node.element.key)))
874                    } else {
875                        None
876                    }
877                })
878                .unwrap_or_default();
879
880            if key.is_none() {
881                break;
882            }
883
884            let key = key.unwrap();
885
886            if let Some(mut entry) = cache.remove(&key) {
887                let weight = entry.policy_weight();
888                Deques::unlink_ao_from_deque(deq_name, deq, &mut entry);
889                Deques::unlink_wo(write_order_deq, &mut entry);
890                evicted_entry_count += 1;
891                evicted_policy_weight = evicted_policy_weight.saturating_add(weight as u64);
892            } else {
893                deq.pop_front();
894            }
895        }
896
897        (evicted_entry_count, evicted_policy_weight)
898    }
899
900    // Returns (u64, u64) where (evicted_entry_count, evicted_policy_weight).
901    #[inline]
902    fn remove_expired_wo(&mut self, batch_size: usize, now: Instant) -> (u64, u64) {
903        let mut evicted_entry_count = 0u64;
904        let mut evicted_policy_weight = 0u64;
905        let time_to_live = &self.time_to_live;
906
907        for _ in 0..batch_size {
908            let key = self
909                .deques
910                .write_order
911                .peek_front()
912                .and_then(|node| {
913                    if Self::is_expired_entry_wo(time_to_live, node, now) {
914                        Some(Some(Rc::clone(&node.element.key)))
915                    } else {
916                        None
917                    }
918                })
919                .unwrap_or_default();
920
921            if key.is_none() {
922                break;
923            }
924
925            let key = key.unwrap();
926
927            if let Some(mut entry) = self.cache.remove(&key) {
928                let weight = entry.policy_weight();
929                self.deques.unlink_ao(&mut entry);
930                Deques::unlink_wo(&mut self.deques.write_order, &mut entry);
931                evicted_entry_count += 1;
932                evicted_policy_weight = evicted_policy_weight.saturating_sub(weight as u64);
933            } else {
934                self.deques.write_order.pop_front();
935            }
936        }
937
938        (evicted_entry_count, evicted_policy_weight)
939    }
940
941    #[inline]
942    fn evict_lru_entries(&mut self) {
943        const DEQ_NAME: &str = "probation";
944
945        let weights_to_evict = self.weights_to_evict();
946        let mut evicted_count = 0u64;
947        let mut evicted_policy_weight = 0u64;
948
949        {
950            let deqs = &mut self.deques;
951            let (probation, wo, cache) =
952                (&mut deqs.probation, &mut deqs.write_order, &mut self.cache);
953
954            for _ in 0..EVICTION_BATCH_SIZE {
955                if evicted_policy_weight >= weights_to_evict {
956                    break;
957                }
958
959                let key = probation
960                    .peek_front()
961                    .map(|node| Rc::clone(&node.element.key));
962
963                if key.is_none() {
964                    break;
965                }
966                let key = key.unwrap();
967
968                if let Some(mut entry) = cache.remove(&key) {
969                    let weight = entry.policy_weight();
970                    Deques::unlink_ao_from_deque(DEQ_NAME, probation, &mut entry);
971                    Deques::unlink_wo(wo, &mut entry);
972                    evicted_count += 1;
973                    evicted_policy_weight = evicted_policy_weight.saturating_add(weight as u64);
974                } else {
975                    probation.pop_front();
976                }
977            }
978        }
979
980        self.entry_count -= evicted_count;
981        self.saturating_sub_from_total_weight(evicted_policy_weight);
982    }
983}
984
985//
986// for testing
987//
988#[cfg(test)]
989impl<K, V, S> Cache<K, V, S>
990where
991    K: Hash + Eq,
992    S: BuildHasher + Clone,
993{
994    fn set_expiration_clock(&mut self, clock: Option<crate::common::time::Clock>) {
995        self.expiration_clock = clock;
996    }
997}
998
999#[derive(Default)]
1000struct EntrySizeAndFrequency {
1001    weight: u64,
1002    freq: u32,
1003}
1004
1005impl EntrySizeAndFrequency {
1006    fn new(policy_weight: u64) -> Self {
1007        Self {
1008            weight: policy_weight,
1009            ..Default::default()
1010        }
1011    }
1012
1013    fn add_policy_weight<K, V>(&mut self, key: &K, value: &V, weigher: &mut Option<Weigher<K, V>>) {
1014        self.weight += weigh(weigher, key, value) as u64;
1015    }
1016
1017    fn add_frequency(&mut self, freq: &FrequencySketch, hash: u64) {
1018        self.freq += freq.frequency(hash) as u32;
1019    }
1020}
1021
1022// Access-Order Queue Node
1023type AoqNode<K> = NonNull<DeqNode<KeyHashDate<K>>>;
1024
1025enum AdmissionResult<K> {
1026    Admitted {
1027        victim_nodes: SmallVec<[AoqNode<K>; 8]>,
1028        victims_weight: u64,
1029    },
1030    Rejected,
1031}
1032
1033//
1034// private free-standing functions
1035//
1036#[inline]
1037fn weigh<K, V>(weigher: &mut Option<Weigher<K, V>>, key: &K, value: &V) -> u32 {
1038    weigher.as_mut().map(|w| w(key, value)).unwrap_or(1)
1039}
1040
1041// To see the debug prints, run test as `cargo test -- --nocapture`
1042#[cfg(test)]
1043mod tests {
1044    use super::Cache;
1045    use crate::common::time::Clock;
1046
1047    use std::time::Duration;
1048
1049    #[test]
1050    fn basic_single_thread() {
1051        let mut cache = Cache::new(3);
1052        cache.enable_frequency_sketch_for_testing();
1053
1054        cache.insert("a", "alice");
1055        cache.insert("b", "bob");
1056        assert_eq!(cache.get(&"a"), Some(&"alice"));
1057        assert!(cache.contains_key(&"a"));
1058        assert!(cache.contains_key(&"b"));
1059        assert_eq!(cache.get(&"b"), Some(&"bob"));
1060        // counts: a -> 1, b -> 1
1061
1062        cache.insert("c", "cindy");
1063        assert_eq!(cache.get(&"c"), Some(&"cindy"));
1064        assert!(cache.contains_key(&"c"));
1065        // counts: a -> 1, b -> 1, c -> 1
1066
1067        assert!(cache.contains_key(&"a"));
1068        assert_eq!(cache.get(&"a"), Some(&"alice"));
1069        assert_eq!(cache.get(&"b"), Some(&"bob"));
1070        assert!(cache.contains_key(&"b"));
1071        // counts: a -> 2, b -> 2, c -> 1
1072
1073        // "d" should not be admitted because its frequency is too low.
1074        cache.insert("d", "david"); //   count: d -> 0
1075        assert_eq!(cache.get(&"d"), None); //   d -> 1
1076        assert!(!cache.contains_key(&"d"));
1077
1078        cache.insert("d", "david");
1079        assert!(!cache.contains_key(&"d"));
1080        assert_eq!(cache.get(&"d"), None); //   d -> 2
1081
1082        // "d" should be admitted and "c" should be evicted
1083        // because d's frequency is higher than c's.
1084        cache.insert("d", "dennis");
1085        assert_eq!(cache.get(&"a"), Some(&"alice"));
1086        assert_eq!(cache.get(&"b"), Some(&"bob"));
1087        assert_eq!(cache.get(&"c"), None);
1088        assert_eq!(cache.get(&"d"), Some(&"dennis"));
1089        assert!(cache.contains_key(&"a"));
1090        assert!(cache.contains_key(&"b"));
1091        assert!(!cache.contains_key(&"c"));
1092        assert!(cache.contains_key(&"d"));
1093
1094        cache.invalidate(&"b");
1095        assert_eq!(cache.get(&"b"), None);
1096        assert!(!cache.contains_key(&"b"));
1097    }
1098
1099    #[test]
1100    fn size_aware_eviction() {
1101        let weigher = |_k: &&str, v: &(&str, u32)| v.1;
1102
1103        let alice = ("alice", 10);
1104        let bob = ("bob", 15);
1105        let bill = ("bill", 20);
1106        let cindy = ("cindy", 5);
1107        let david = ("david", 15);
1108        let dennis = ("dennis", 15);
1109
1110        let mut cache = Cache::builder().max_capacity(31).weigher(weigher).build();
1111        cache.enable_frequency_sketch_for_testing();
1112
1113        cache.insert("a", alice);
1114        cache.insert("b", bob);
1115        assert_eq!(cache.get(&"a"), Some(&alice));
1116        assert!(cache.contains_key(&"a"));
1117        assert!(cache.contains_key(&"b"));
1118        assert_eq!(cache.get(&"b"), Some(&bob));
1119        // order (LRU -> MRU) and counts: a -> 1, b -> 1
1120
1121        cache.insert("c", cindy);
1122        assert_eq!(cache.get(&"c"), Some(&cindy));
1123        assert!(cache.contains_key(&"c"));
1124        // order and counts: a -> 1, b -> 1, c -> 1
1125
1126        assert!(cache.contains_key(&"a"));
1127        assert_eq!(cache.get(&"a"), Some(&alice));
1128        assert_eq!(cache.get(&"b"), Some(&bob));
1129        assert!(cache.contains_key(&"b"));
1130        // order and counts: c -> 1, a -> 2, b -> 2
1131
1132        // To enter "d" (weight: 15), it needs to evict "c" (w: 5) and "a" (w: 10).
1133        // "d" must have higher count than 3, which is the aggregated count
1134        // of "a" and "c".
1135        cache.insert("d", david); //   count: d -> 0
1136        assert_eq!(cache.get(&"d"), None); //   d -> 1
1137        assert!(!cache.contains_key(&"d"));
1138
1139        cache.insert("d", david);
1140        assert!(!cache.contains_key(&"d"));
1141        assert_eq!(cache.get(&"d"), None); //   d -> 2
1142
1143        cache.insert("d", david);
1144        assert_eq!(cache.get(&"d"), None); //   d -> 3
1145        assert!(!cache.contains_key(&"d"));
1146
1147        cache.insert("d", david);
1148        assert!(!cache.contains_key(&"d"));
1149        assert_eq!(cache.get(&"d"), None); //   d -> 4
1150
1151        // Finally "d" should be admitted by evicting "c" and "a".
1152        cache.insert("d", dennis);
1153        assert_eq!(cache.get(&"a"), None);
1154        assert_eq!(cache.get(&"b"), Some(&bob));
1155        assert_eq!(cache.get(&"c"), None);
1156        assert_eq!(cache.get(&"d"), Some(&dennis));
1157        assert!(!cache.contains_key(&"a"));
1158        assert!(cache.contains_key(&"b"));
1159        assert!(!cache.contains_key(&"c"));
1160        assert!(cache.contains_key(&"d"));
1161
1162        // Update "b" with "bill" (w: 15 -> 20). This should evict "d" (w: 15).
1163        cache.insert("b", bill);
1164        assert_eq!(cache.get(&"b"), Some(&bill));
1165        assert_eq!(cache.get(&"d"), None);
1166        assert!(cache.contains_key(&"b"));
1167        assert!(!cache.contains_key(&"d"));
1168
1169        // Re-add "a" (w: 10) and update "b" with "bob" (w: 20 -> 15).
1170        cache.insert("a", alice);
1171        cache.insert("b", bob);
1172        assert_eq!(cache.get(&"a"), Some(&alice));
1173        assert_eq!(cache.get(&"b"), Some(&bob));
1174        assert_eq!(cache.get(&"d"), None);
1175        assert!(cache.contains_key(&"a"));
1176        assert!(cache.contains_key(&"b"));
1177        assert!(!cache.contains_key(&"d"));
1178
1179        // Verify the sizes.
1180        assert_eq!(cache.entry_count(), 2);
1181        assert_eq!(cache.weighted_size(), 25);
1182    }
1183
1184    #[test]
1185    fn invalidate_all() {
1186        let mut cache = Cache::new(100);
1187        cache.enable_frequency_sketch_for_testing();
1188
1189        cache.insert("a", "alice");
1190        cache.insert("b", "bob");
1191        cache.insert("c", "cindy");
1192        assert_eq!(cache.get(&"a"), Some(&"alice"));
1193        assert_eq!(cache.get(&"b"), Some(&"bob"));
1194        assert_eq!(cache.get(&"c"), Some(&"cindy"));
1195        assert!(cache.contains_key(&"a"));
1196        assert!(cache.contains_key(&"b"));
1197        assert!(cache.contains_key(&"c"));
1198
1199        cache.invalidate_all();
1200
1201        cache.insert("d", "david");
1202
1203        assert!(cache.get(&"a").is_none());
1204        assert!(cache.get(&"b").is_none());
1205        assert!(cache.get(&"c").is_none());
1206        assert_eq!(cache.get(&"d"), Some(&"david"));
1207        assert!(!cache.contains_key(&"a"));
1208        assert!(!cache.contains_key(&"b"));
1209        assert!(!cache.contains_key(&"c"));
1210        assert!(cache.contains_key(&"d"));
1211    }
1212
1213    #[test]
1214    fn invalidate_entries_if() {
1215        use std::collections::HashSet;
1216
1217        let mut cache = Cache::new(100);
1218        cache.enable_frequency_sketch_for_testing();
1219
1220        let (clock, mock) = Clock::mock();
1221        cache.set_expiration_clock(Some(clock));
1222
1223        cache.insert(0, "alice");
1224        cache.insert(1, "bob");
1225        cache.insert(2, "alex");
1226
1227        mock.increment(Duration::from_secs(5)); // 5 secs from the start.
1228
1229        assert_eq!(cache.get(&0), Some(&"alice"));
1230        assert_eq!(cache.get(&1), Some(&"bob"));
1231        assert_eq!(cache.get(&2), Some(&"alex"));
1232        assert!(cache.contains_key(&0));
1233        assert!(cache.contains_key(&1));
1234        assert!(cache.contains_key(&2));
1235
1236        let names = ["alice", "alex"].iter().cloned().collect::<HashSet<_>>();
1237        cache.invalidate_entries_if(move |_k, &v| names.contains(v));
1238
1239        mock.increment(Duration::from_secs(5)); // 10 secs from the start.
1240
1241        cache.insert(3, "alice");
1242
1243        assert!(cache.get(&0).is_none());
1244        assert!(cache.get(&2).is_none());
1245        assert_eq!(cache.get(&1), Some(&"bob"));
1246        // This should survive as it was inserted after calling invalidate_entries_if.
1247        assert_eq!(cache.get(&3), Some(&"alice"));
1248
1249        assert!(!cache.contains_key(&0));
1250        assert!(cache.contains_key(&1));
1251        assert!(!cache.contains_key(&2));
1252        assert!(cache.contains_key(&3));
1253
1254        assert_eq!(cache.cache.len(), 2);
1255
1256        mock.increment(Duration::from_secs(5)); // 15 secs from the start.
1257
1258        cache.invalidate_entries_if(|_k, &v| v == "alice");
1259        cache.invalidate_entries_if(|_k, &v| v == "bob");
1260
1261        assert!(cache.get(&1).is_none());
1262        assert!(cache.get(&3).is_none());
1263
1264        assert!(!cache.contains_key(&1));
1265        assert!(!cache.contains_key(&3));
1266
1267        assert_eq!(cache.cache.len(), 0);
1268    }
1269
1270    #[test]
1271    fn time_to_live() {
1272        let mut cache = Cache::builder()
1273            .max_capacity(100)
1274            .time_to_live(Duration::from_secs(10))
1275            .build();
1276        cache.enable_frequency_sketch_for_testing();
1277
1278        let (clock, mock) = Clock::mock();
1279        cache.set_expiration_clock(Some(clock));
1280
1281        cache.insert("a", "alice");
1282
1283        mock.increment(Duration::from_secs(5)); // 5 secs from the start.
1284
1285        assert_eq!(cache.get(&"a"), Some(&"alice"));
1286        assert!(cache.contains_key(&"a"));
1287
1288        mock.increment(Duration::from_secs(5)); // 10 secs.
1289
1290        assert_eq!(cache.get(&"a"), None);
1291        assert!(!cache.contains_key(&"a"));
1292        assert_eq!(cache.iter().count(), 0);
1293        assert!(cache.cache.is_empty());
1294
1295        cache.insert("b", "bob");
1296
1297        assert_eq!(cache.cache.len(), 1);
1298
1299        mock.increment(Duration::from_secs(5)); // 15 secs.
1300
1301        assert_eq!(cache.get(&"b"), Some(&"bob"));
1302        assert!(cache.contains_key(&"b"));
1303        assert_eq!(cache.cache.len(), 1);
1304
1305        cache.insert("b", "bill");
1306
1307        mock.increment(Duration::from_secs(5)); // 20 secs
1308
1309        assert_eq!(cache.get(&"b"), Some(&"bill"));
1310        assert!(cache.contains_key(&"b"));
1311        assert_eq!(cache.cache.len(), 1);
1312
1313        mock.increment(Duration::from_secs(5)); // 25 secs
1314
1315        assert_eq!(cache.get(&"a"), None);
1316        assert_eq!(cache.get(&"b"), None);
1317        assert!(!cache.contains_key(&"a"));
1318        assert!(!cache.contains_key(&"b"));
1319        assert_eq!(cache.iter().count(), 0);
1320        assert!(cache.cache.is_empty());
1321    }
1322
1323    #[test]
1324    fn time_to_idle() {
1325        let mut cache = Cache::builder()
1326            .max_capacity(100)
1327            .time_to_idle(Duration::from_secs(10))
1328            .build();
1329        cache.enable_frequency_sketch_for_testing();
1330
1331        let (clock, mock) = Clock::mock();
1332        cache.set_expiration_clock(Some(clock));
1333
1334        cache.insert("a", "alice");
1335
1336        mock.increment(Duration::from_secs(5)); // 5 secs from the start.
1337
1338        assert_eq!(cache.get(&"a"), Some(&"alice"));
1339
1340        mock.increment(Duration::from_secs(5)); // 10 secs.
1341
1342        cache.insert("b", "bob");
1343
1344        assert_eq!(cache.cache.len(), 2);
1345
1346        mock.increment(Duration::from_secs(2)); // 12 secs.
1347
1348        // contains_key does not reset the idle timer for the key.
1349        assert!(cache.contains_key(&"a"));
1350        assert!(cache.contains_key(&"b"));
1351
1352        assert_eq!(cache.cache.len(), 2);
1353
1354        mock.increment(Duration::from_secs(3)); // 15 secs.
1355
1356        assert_eq!(cache.get(&"a"), None);
1357        assert_eq!(cache.get(&"b"), Some(&"bob"));
1358        assert!(!cache.contains_key(&"a"));
1359        assert!(cache.contains_key(&"b"));
1360        assert_eq!(cache.iter().count(), 1);
1361        assert_eq!(cache.cache.len(), 1);
1362
1363        mock.increment(Duration::from_secs(10)); // 25 secs
1364
1365        assert_eq!(cache.get(&"a"), None);
1366        assert_eq!(cache.get(&"b"), None);
1367        assert!(!cache.contains_key(&"a"));
1368        assert!(!cache.contains_key(&"b"));
1369        assert_eq!(cache.iter().count(), 0);
1370        assert!(cache.cache.is_empty());
1371    }
1372
1373    #[cfg_attr(target_pointer_width = "16", ignore)]
1374    #[test]
1375    fn test_skt_capacity_will_not_overflow() {
1376        // power of two
1377        let pot = |exp| 2u64.pow(exp);
1378
1379        let ensure_sketch_len = |max_capacity, len, name| {
1380            let mut cache = Cache::<u8, u8>::new(max_capacity);
1381            cache.enable_frequency_sketch_for_testing();
1382            assert_eq!(cache.frequency_sketch.table_len(), len as usize, "{}", name);
1383        };
1384
1385        if cfg!(target_pointer_width = "32") {
1386            let pot24 = pot(24);
1387            let pot16 = pot(16);
1388            ensure_sketch_len(0, 128, "0");
1389            ensure_sketch_len(128, 128, "128");
1390            ensure_sketch_len(pot16, pot16, "pot16");
1391            // due to ceiling to next_power_of_two
1392            ensure_sketch_len(pot16 + 1, pot(17), "pot16 + 1");
1393            // due to ceiling to next_power_of_two
1394            ensure_sketch_len(pot24 - 1, pot24, "pot24 - 1");
1395            ensure_sketch_len(pot24, pot24, "pot24");
1396            ensure_sketch_len(pot(27), pot24, "pot(27)");
1397            ensure_sketch_len(u32::MAX as u64, pot24, "u32::MAX");
1398        } else {
1399            // target_pointer_width: 64 or larger.
1400            let pot30 = pot(30);
1401            let pot16 = pot(16);
1402            ensure_sketch_len(0, 128, "0");
1403            ensure_sketch_len(128, 128, "128");
1404            ensure_sketch_len(pot16, pot16, "pot16");
1405            // due to ceiling to next_power_of_two
1406            ensure_sketch_len(pot16 + 1, pot(17), "pot16 + 1");
1407
1408            // The following tests will allocate large memory (~8GiB).
1409            // Skip when running on Circle CI.
1410            if !cfg!(circleci) {
1411                // due to ceiling to next_power_of_two
1412                ensure_sketch_len(pot30 - 1, pot30, "pot30- 1");
1413                ensure_sketch_len(pot30, pot30, "pot30");
1414                ensure_sketch_len(u64::MAX, pot30, "u64::MAX");
1415            }
1416        };
1417    }
1418
1419    #[test]
1420    fn test_debug_format() {
1421        let mut cache = Cache::new(10);
1422        cache.insert('a', "alice");
1423        cache.insert('b', "bob");
1424        cache.insert('c', "cindy");
1425
1426        let debug_str = format!("{:?}", cache);
1427        assert!(debug_str.starts_with('{'));
1428        assert!(debug_str.contains(r#"'a': "alice""#));
1429        assert!(debug_str.contains(r#"'b': "bob""#));
1430        assert!(debug_str.contains(r#"'c': "cindy""#));
1431        assert!(debug_str.ends_with('}'));
1432    }
1433}