Skip to main content

micro_moka/unsync/
cache.rs

1use super::{deques::Deques, CacheBuilder, Iter, KeyHashDate, ValueEntry};
2use crate::{
3    common::{self, deque::DeqNode, frequency_sketch::FrequencySketch, CacheRegion},
4    Policy,
5};
6
7use smallvec::SmallVec;
8use std::{
9    borrow::Borrow,
10    collections::{hash_map::RandomState, HashMap},
11    fmt,
12    hash::{BuildHasher, Hash},
13    ptr::NonNull,
14    rc::Rc,
15};
16
17const EVICTION_BATCH_SIZE: usize = 100;
18
19type CacheStore<K, V, S> = std::collections::HashMap<Rc<K>, ValueEntry<K, V>, S>;
20
21/// An in-memory cache that is _not_ thread-safe.
22///
23/// `Cache` utilizes a hash table [`std::collections::HashMap`][std-hashmap] from the
24/// standard library for the central key-value storage. `Cache` performs a
25/// best-effort bounding of the map using an entry replacement algorithm to determine
26/// which entries to evict when the capacity is exceeded.
27///
28/// [std-hashmap]: https://doc.rust-lang.org/std/collections/struct.HashMap.html
29///
30/// # Characteristic difference between `unsync` and `sync`/`future` caches
31///
32/// If you use a cache from a single thread application, `unsync::Cache` may
33/// outperform other caches for updates and retrievals because other caches have some
34/// overhead on syncing internal data structures between threads.
35///
36/// # Examples
37///
38/// Cache entries are manually added using the insert method, and are stored in the
39/// cache until either evicted or manually invalidated.
40///
41/// Here's an example of reading and updating a cache by using the main thread:
42///
43///```rust
44/// use micro_moka::unsync::Cache;
45///
46/// const NUM_KEYS: usize = 64;
47///
48/// fn value(n: usize) -> String {
49///     format!("value {}", n)
50/// }
51///
52/// // Create a cache that can store up to 10,000 entries.
53/// let mut cache = Cache::new(10_000);
54///
55/// // Insert 64 entries.
56/// for key in 0..NUM_KEYS {
57///     cache.insert(key, value(key));
58/// }
59///
60/// // Invalidate every 4 element of the inserted entries.
61/// for key in (0..NUM_KEYS).step_by(4) {
62///     cache.invalidate(&key);
63/// }
64///
65/// // Verify the result.
66/// for key in 0..NUM_KEYS {
67///     if key % 4 == 0 {
68///         assert_eq!(cache.get(&key), None);
69///     } else {
70///         assert_eq!(cache.get(&key), Some(&value(key)));
71///     }
72/// }
73/// ```
74///
75/// # Hashing Algorithm
76///
77/// By default, `Cache` uses a hashing algorithm selected to provide resistance
78/// against HashDoS attacks. It will the same one used by
79/// `std::collections::HashMap`, which is currently SipHash 1-3.
80///
81/// While SipHash's performance is very competitive for medium sized keys, other
82/// hashing algorithms will outperform it for small keys such as integers as well as
83/// large keys such as long strings. However those algorithms will typically not
84/// protect against attacks such as HashDoS.
85///
86/// The hashing algorithm can be replaced on a per-`Cache` basis using the
87/// [`build_with_hasher`][build-with-hasher-method] method of the
88/// `CacheBuilder`. Many alternative algorithms are available on crates.io, such
89/// as the [aHash][ahash-crate] crate.
90///
91/// [build-with-hasher-method]: ./struct.CacheBuilder.html#method.build_with_hasher
92/// [ahash-crate]: https://crates.io/crates/ahash
93///
94pub struct Cache<K, V, S = RandomState> {
95    max_capacity: Option<u64>,
96    entry_count: u64,
97    cache: CacheStore<K, V, S>,
98    build_hasher: S,
99    deques: Deques<K>,
100    frequency_sketch: FrequencySketch,
101    frequency_sketch_enabled: bool,
102}
103
104impl<K, V, S> fmt::Debug for Cache<K, V, S>
105where
106    K: fmt::Debug + Eq + Hash,
107    V: fmt::Debug,
108    // TODO: Remove these bounds from S.
109    S: BuildHasher + Clone,
110{
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        let mut d_map = f.debug_map();
113
114        for (k, v) in self.iter() {
115            d_map.entry(&k, &v);
116        }
117
118        d_map.finish()
119    }
120}
121
122impl<K, V> Cache<K, V, RandomState>
123where
124    K: Hash + Eq,
125{
126    /// Constructs a new `Cache<K, V>` that will store up to the `max_capacity` entries.
127    ///
128    /// To adjust various configuration knobs such as `initial_capacity`, use the
129    /// [`CacheBuilder`][builder-struct].
130    ///
131    /// [builder-struct]: ./struct.CacheBuilder.html
132    pub fn new(max_capacity: u64) -> Self {
133        let build_hasher = RandomState::default();
134        Self::with_everything(Some(max_capacity), None, build_hasher)
135    }
136
137    /// Returns a [`CacheBuilder`][builder-struct], which can builds a `Cache` with
138    /// various configuration knobs.
139    ///
140    /// [builder-struct]: ./struct.CacheBuilder.html
141    pub fn builder() -> CacheBuilder<K, V, Cache<K, V, RandomState>> {
142        CacheBuilder::default()
143    }
144}
145
146//
147// public
148//
149impl<K, V, S> Cache<K, V, S> {
150    /// Returns a read-only cache policy of this cache.
151    ///
152    /// At this time, cache policy cannot be modified after cache creation.
153    /// A future version may support to modify it.
154    pub fn policy(&self) -> Policy {
155        Policy::new(self.max_capacity)
156    }
157
158    /// Returns the number of entries in this cache.
159    ///
160    /// # Example
161    ///
162    /// ```rust
163    /// use micro_moka::unsync::Cache;
164    ///
165    /// let mut cache = Cache::new(10);
166    /// cache.insert('n', "Netherland Dwarf");
167    /// cache.insert('l', "Lop Eared");
168    /// cache.insert('d', "Dutch");
169    ///
170    /// // Ensure an entry exists.
171    /// assert!(cache.contains_key(&'n'));
172    ///
173    /// // Followings will print the actual numbers.
174    /// println!("{}", cache.entry_count());   // -> 3
175    /// ```
176    ///
177    pub fn entry_count(&self) -> u64 {
178        self.entry_count
179    }
180
181    /// Returns the total weighted size of entries in this cache.
182    ///
183    /// This is equivalent to `entry_count` as weight support has been removed.
184    pub fn weighted_size(&self) -> u64 {
185        self.entry_count
186    }
187}
188
189impl<K, V, S> Cache<K, V, S>
190where
191    K: Hash + Eq,
192    S: BuildHasher + Clone,
193{
194    pub(crate) fn with_everything(
195        max_capacity: Option<u64>,
196        initial_capacity: Option<usize>,
197        build_hasher: S,
198    ) -> Self {
199        let cache = HashMap::with_capacity_and_hasher(
200            initial_capacity.unwrap_or_default(),
201            build_hasher.clone(),
202        );
203
204        Self {
205            max_capacity,
206            entry_count: 0,
207            cache,
208            build_hasher,
209            deques: Default::default(),
210            frequency_sketch: Default::default(),
211            frequency_sketch_enabled: false,
212        }
213    }
214
215    /// Returns `true` if the cache contains a value for the key.
216    ///
217    /// Unlike the `get` method, this method is not considered a cache read operation,
218    /// so it does not update the historic popularity estimator.
219    ///
220    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
221    /// on the borrowed form _must_ match those for the key type.
222    pub fn contains_key<Q>(&mut self, key: &Q) -> bool
223    where
224        Rc<K>: Borrow<Q>,
225        Q: Hash + Eq + ?Sized,
226    {
227        self.evict_lru_entries();
228        self.cache.contains_key(key)
229    }
230
231    /// Returns an immutable reference of the value corresponding to the key.
232    ///
233    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
234    /// on the borrowed form _must_ match those for the key type.
235    pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
236    where
237        Rc<K>: Borrow<Q>,
238        Q: Hash + Eq + ?Sized,
239    {
240        self.evict_lru_entries();
241        self.frequency_sketch.increment(self.hash(key));
242
243        if let Some(entry) = self.cache.get_mut(key) {
244            Self::record_hit(&mut self.deques, entry);
245            Some(&entry.value)
246        } else {
247            None
248        }
249    }
250
251    /// Inserts a key-value pair into the cache.
252    ///
253    /// If the cache has this key present, the value is updated.
254    pub fn insert(&mut self, key: K, value: V) {
255        self.evict_lru_entries();
256        let policy_weight = 1;
257        let key = Rc::new(key);
258        let entry = ValueEntry::new(value);
259
260        if let Some(old_entry) = self.cache.insert(Rc::clone(&key), entry) {
261            self.handle_update(key, policy_weight, old_entry);
262        } else {
263            let hash = self.hash(&key);
264            self.handle_insert(key, hash, policy_weight);
265        }
266    }
267
268    /// Discards any cached value for the key.
269    ///
270    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
271    /// on the borrowed form _must_ match those for the key type.
272    pub fn invalidate<Q>(&mut self, key: &Q)
273    where
274        Rc<K>: Borrow<Q>,
275        Q: Hash + Eq + ?Sized,
276    {
277        self.evict_lru_entries();
278
279        if let Some(mut entry) = self.cache.remove(key) {
280            self.deques.unlink_ao(&mut entry);
281            self.entry_count -= 1;
282        }
283    }
284
285    /// Discards any cached value for the key, returning the cached value.
286    ///
287    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
288    /// on the borrowed form _must_ match those for the key type.
289    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
290    where
291        Rc<K>: Borrow<Q>,
292        Q: Hash + Eq + ?Sized,
293    {
294        self.evict_lru_entries();
295
296        if let Some(mut entry) = self.cache.remove(key) {
297            self.deques.unlink_ao(&mut entry);
298            self.entry_count -= 1;
299            Some(entry.value)
300        } else {
301            None
302        }
303    }
304
305    /// Discards all cached values.
306    ///
307    /// Like the `invalidate` method, this method does not clear the historic
308    /// popularity estimator of keys so that it retains the client activities of
309    /// trying to retrieve an item.
310    pub fn invalidate_all(&mut self) {
311        // Phase 1: swap out the cache before resetting internal state so that
312        // a panic in V::drop leaves `self` in a consistent (empty) state.
313        let old_capacity = self.cache.capacity();
314        let old_cache = std::mem::replace(
315            &mut self.cache,
316            HashMap::with_hasher(self.build_hasher.clone()),
317        );
318        self.deques.clear();
319        self.entry_count = 0;
320
321        // If V::drop panics, `self` is already in a valid empty state.
322        drop(old_cache);
323
324        // Phase 2: best effort capacity restoration for future inserts.
325        let _ = self.cache.try_reserve(old_capacity);
326    }
327
328    /// Discards cached values that satisfy a predicate.
329    ///
330    /// `invalidate_entries_if` takes a closure that returns `true` or `false`.
331    /// `invalidate_entries_if` will apply the closure to each cached value,
332    /// and if the closure returns `true`, the value will be invalidated.
333    ///
334    /// Like the `invalidate` method, this method does not clear the historic
335    /// popularity estimator of keys so that it retains the client activities of
336    /// trying to retrieve an item.
337    // -----------------------------------------------------------------------
338    // (The followings are not doc comments)
339    // We need this #[allow(...)] to avoid a false Clippy warning about needless
340    // collect to create keys_to_invalidate.
341    // clippy 0.1.52 (9a1dfd2dc5c 2021-04-30) in Rust 1.52.0-beta.7
342    #[allow(clippy::needless_collect)]
343    pub fn invalidate_entries_if(&mut self, mut predicate: impl FnMut(&K, &V) -> bool) {
344        let Self { cache, deques, .. } = self;
345
346        // Since we can't do cache.iter() and cache.remove() at the same time,
347        // invalidation needs to run in two steps:
348        // 1. Examine all entries in this cache and collect keys to invalidate.
349        // 2. Remove entries for the keys.
350
351        let keys_to_invalidate = cache
352            .iter()
353            .filter(|(key, entry)| (predicate)(key, &entry.value))
354            .map(|(key, _)| Rc::clone(key))
355            .collect::<Vec<_>>();
356
357        let mut invalidated = 0u64;
358
359        keys_to_invalidate.into_iter().for_each(|k| {
360            if let Some(mut entry) = cache.remove(&k) {
361                let _weight = entry.policy_weight();
362                deques.unlink_ao(&mut entry);
363                invalidated += 1;
364            }
365        });
366        self.entry_count -= invalidated;
367    }
368
369    /// Creates an iterator visiting all key-value pairs in arbitrary order. The
370    /// iterator element type is `(&K, &V)`.
371    ///
372    /// Unlike the `get` method, visiting entries via an iterator do not update the
373    /// historic popularity estimator or reset idle timers for keys.
374    ///
375    /// # Examples
376    ///
377    /// ```rust
378    /// use micro_moka::unsync::Cache;
379    ///
380    /// let mut cache = Cache::new(100);
381    /// cache.insert("Julia", 14);
382    ///
383    /// let mut iter = cache.iter();
384    /// let (k, v) = iter.next().unwrap(); // (&K, &V)
385    /// assert_eq!(k, &"Julia");
386    /// assert_eq!(v, &14);
387    ///
388    /// assert!(iter.next().is_none());
389    /// ```
390    ///
391    pub fn iter(&self) -> Iter<'_, K, V> {
392        Iter::new(self, self.cache.iter())
393    }
394}
395
396//
397// private
398//
399impl<K, V, S> Cache<K, V, S>
400where
401    K: Hash + Eq,
402    S: BuildHasher + Clone,
403{
404    #[inline]
405    fn hash<Q>(&self, key: &Q) -> u64
406    where
407        Rc<K>: Borrow<Q>,
408        Q: Hash + Eq + ?Sized,
409    {
410        self.build_hasher.hash_one(key)
411    }
412
413    fn record_hit(deques: &mut Deques<K>, entry: &mut ValueEntry<K, V>) {
414        deques.move_to_back_ao(entry)
415    }
416
417    fn has_enough_capacity(&self, candidate_weight: u32, ws: u64) -> bool {
418        self.max_capacity
419            .map(|limit| ws + candidate_weight as u64 <= limit)
420            .unwrap_or(true)
421    }
422
423    fn weights_to_evict(&self) -> u64 {
424        self.max_capacity
425            .map(|limit| self.entry_count.saturating_sub(limit))
426            .unwrap_or_default()
427    }
428
429    #[inline]
430    fn should_enable_frequency_sketch(&self) -> bool {
431        if self.frequency_sketch_enabled {
432            false
433        } else if let Some(max_cap) = self.max_capacity {
434            self.entry_count >= max_cap / 2
435        } else {
436            false
437        }
438    }
439
440    #[inline]
441    fn enable_frequency_sketch(&mut self) {
442        if let Some(max_cap) = self.max_capacity {
443            self.do_enable_frequency_sketch(max_cap);
444        }
445    }
446
447    #[cfg(test)]
448    fn enable_frequency_sketch_for_testing(&mut self) {
449        if let Some(max_cap) = self.max_capacity {
450            self.do_enable_frequency_sketch(max_cap);
451        }
452    }
453
454    #[inline]
455    fn do_enable_frequency_sketch(&mut self, cache_capacity: u64) {
456        let skt_capacity = common::sketch_capacity(cache_capacity);
457        self.frequency_sketch.ensure_capacity(skt_capacity);
458        self.frequency_sketch_enabled = true;
459    }
460
461    #[inline]
462    fn handle_insert(&mut self, key: Rc<K>, hash: u64, policy_weight: u32) {
463        let has_free_space = self.has_enough_capacity(policy_weight, self.entry_count);
464        let (cache, deqs, freq) = (&mut self.cache, &mut self.deques, &self.frequency_sketch);
465
466        if has_free_space {
467            // Add the candidate to the deque.
468            let key = Rc::clone(&key);
469            let entry = cache.get_mut(&key).unwrap();
470            deqs.push_back_ao(
471                CacheRegion::MainProbation,
472                KeyHashDate::new(Rc::clone(&key), hash),
473                entry,
474            );
475            self.entry_count += 1;
476            // self.saturating_add_to_total_weight(policy_weight as u64);
477
478            if self.should_enable_frequency_sketch() {
479                self.enable_frequency_sketch();
480            }
481
482            return;
483        }
484
485        if let Some(max) = self.max_capacity {
486            if policy_weight as u64 > max {
487                // The candidate is too big to fit in the cache. Reject it.
488                cache.remove(&Rc::clone(&key));
489                return;
490            }
491        }
492
493        let mut candidate = EntrySizeAndFrequency::new(policy_weight as u64);
494        candidate.add_frequency(freq, hash);
495
496        match Self::admit(&candidate, cache, deqs, freq) {
497            AdmissionResult::Admitted { victim_nodes } => {
498                // Remove the victims from the cache (hash map) and deque.
499                for victim in victim_nodes {
500                    // Remove the victim from the hash map.
501                    let mut vic_entry = cache
502                        .remove(unsafe { &victim.as_ref().element.key })
503                        .expect("Cannot remove a victim from the hash map");
504                    // And then remove the victim from the deques.
505                    deqs.unlink_ao(&mut vic_entry);
506                    // Deques::unlink_wo(&mut deqs.write_order, &mut vic_entry);
507                    self.entry_count -= 1;
508                }
509
510                // Add the candidate to the deque.
511                let entry = cache.get_mut(&key).unwrap();
512                let key = Rc::clone(&key);
513                deqs.push_back_ao(
514                    CacheRegion::MainProbation,
515                    KeyHashDate::new(Rc::clone(&key), hash),
516                    entry,
517                );
518
519                self.entry_count += 1;
520                // Self::saturating_sub_from_total_weight(self, victims_weight);
521                // Self::saturating_add_to_total_weight(self, policy_weight as u64);
522
523                if self.should_enable_frequency_sketch() {
524                    self.enable_frequency_sketch();
525                }
526            }
527            AdmissionResult::Rejected => {
528                // Remove the candidate from the cache.
529                cache.remove(&key);
530            }
531        }
532    }
533
534    /// Performs size-aware admission explained in the paper:
535    /// [Lightweight Robust Size Aware Cache Management][size-aware-cache-paper]
536    /// by Gil Einziger, Ohad Eytan, Roy Friedman, Ben Manes.
537    ///
538    /// [size-aware-cache-paper]: https://arxiv.org/abs/2105.08770
539    ///
540    /// There are some modifications in this implementation:
541    /// - To admit to the main space, candidate's frequency must be higher than
542    ///   the aggregated frequencies of the potential victims. (In the paper,
543    ///   `>=` operator is used rather than `>`)  The `>` operator will do a better
544    ///   job to prevent the main space from polluting.
545    /// - When a candidate is rejected, the potential victims will stay at the LRU
546    ///   position of the probation access-order queue. (In the paper, they will be
547    ///   promoted (to the MRU position?) to force the eviction policy to select a
548    ///   different set of victims for the next candidate). We may implement the
549    ///   paper's behavior later?
550    ///
551    #[inline]
552    fn admit(
553        candidate: &EntrySizeAndFrequency,
554        _cache: &CacheStore<K, V, S>,
555        deqs: &Deques<K>,
556        freq: &FrequencySketch,
557    ) -> AdmissionResult<K> {
558        let mut victims = EntrySizeAndFrequency::default();
559        let mut victim_nodes = SmallVec::default();
560
561        // Get first potential victim at the LRU position.
562        let mut next_victim = deqs.probation.peek_front_ptr();
563
564        // Aggregate potential victims.
565        while victims.weight < candidate.weight {
566            if candidate.freq < victims.freq {
567                break;
568            }
569            if let Some(victim) = next_victim.take() {
570                next_victim = DeqNode::next_node_ptr(victim);
571                let vic_elem = &unsafe { victim.as_ref() }.element;
572
573                // let vic_entry = cache
574                //     .get(&vic_elem.key)
575                //     .expect("Cannot get an victim entry");
576                victims.add_policy_weight();
577                victims.add_frequency(freq, vic_elem.hash);
578                victim_nodes.push(victim);
579            } else {
580                // No more potential victims.
581                break;
582            }
583        }
584
585        // Admit or reject the candidate.
586
587        // TODO: Implement some randomness to mitigate hash DoS attack.
588        // See Caffeine's implementation.
589
590        if victims.weight >= candidate.weight && candidate.freq > victims.freq {
591            AdmissionResult::Admitted { victim_nodes }
592        } else {
593            AdmissionResult::Rejected
594        }
595    }
596
597    fn handle_update(&mut self, key: Rc<K>, policy_weight: u32, old_entry: ValueEntry<K, V>) {
598        let entry = self.cache.get_mut(&key).unwrap();
599        entry.replace_deq_nodes_with(old_entry);
600        entry.set_policy_weight(policy_weight);
601
602        let deqs = &mut self.deques;
603        deqs.move_to_back_ao(entry);
604
605        // self.saturating_sub_from_total_weight(old_policy_weight as u64);
606        // self.saturating_add_to_total_weight(policy_weight as u64);
607    }
608
609    #[inline]
610    fn evict_lru_entries(&mut self) {
611        const DEQ_NAME: &str = "probation";
612
613        let weights_to_evict = self.weights_to_evict();
614        let mut evicted_count = 0u64;
615        let mut evicted_policy_weight = 0u64;
616
617        {
618            let deqs = &mut self.deques;
619            let (probation, cache) = (&mut deqs.probation, &mut self.cache);
620
621            for _ in 0..EVICTION_BATCH_SIZE {
622                if evicted_policy_weight >= weights_to_evict {
623                    break;
624                }
625
626                // clippy::map_clone will give us a false positive warning here.
627                // Version: clippy 0.1.77 (f2048098a1c 2024-02-09) in Rust 1.77.0-beta.2
628                #[allow(clippy::map_clone)]
629                let key = probation
630                    .peek_front()
631                    .map(|node| Rc::clone(&node.element.key));
632
633                if key.is_none() {
634                    break;
635                }
636                let key = key.unwrap();
637
638                if let Some(mut entry) = cache.remove(&key) {
639                    let weight = entry.policy_weight();
640                    Deques::unlink_ao_from_deque(DEQ_NAME, probation, &mut entry);
641                    evicted_count += 1;
642                    evicted_policy_weight = evicted_policy_weight.saturating_add(weight as u64);
643                } else {
644                    probation.pop_front();
645                }
646            }
647        }
648
649        self.entry_count -= evicted_count;
650        // self.saturating_sub_from_total_weight(evicted_policy_weight);
651    }
652}
653
654//
655// for testing
656//
657#[cfg(test)]
658impl<K, V, S> Cache<K, V, S>
659where
660    K: Hash + Eq,
661    S: BuildHasher + Clone,
662{
663}
664
665#[derive(Default)]
666struct EntrySizeAndFrequency {
667    weight: u64,
668    freq: u32,
669}
670
671impl EntrySizeAndFrequency {
672    fn new(policy_weight: u64) -> Self {
673        Self {
674            weight: policy_weight,
675            ..Default::default()
676        }
677    }
678
679    fn add_policy_weight(&mut self) {
680        self.weight += 1;
681    }
682
683    fn add_frequency(&mut self, freq: &FrequencySketch, hash: u64) {
684        self.freq += freq.frequency(hash) as u32;
685    }
686}
687
688// Access-Order Queue Node
689type AoqNode<K> = NonNull<DeqNode<KeyHashDate<K>>>;
690
691enum AdmissionResult<K> {
692    Admitted {
693        victim_nodes: SmallVec<[AoqNode<K>; 8]>,
694    },
695    Rejected,
696}
697
698//
699// private free-standing functions
700//
701
702// To see the debug prints, run test as `cargo test -- --nocapture`
703#[cfg(test)]
704mod tests {
705    use super::Cache;
706
707    #[test]
708    fn basic_single_thread() {
709        let mut cache = Cache::new(3);
710        cache.enable_frequency_sketch_for_testing();
711
712        cache.insert("a", "alice");
713        cache.insert("b", "bob");
714        assert_eq!(cache.get(&"a"), Some(&"alice"));
715        assert!(cache.contains_key(&"a"));
716        assert!(cache.contains_key(&"b"));
717        assert_eq!(cache.get(&"b"), Some(&"bob"));
718        // counts: a -> 1, b -> 1
719
720        cache.insert("c", "cindy");
721        assert_eq!(cache.get(&"c"), Some(&"cindy"));
722        assert!(cache.contains_key(&"c"));
723        // counts: a -> 1, b -> 1, c -> 1
724
725        assert!(cache.contains_key(&"a"));
726        assert_eq!(cache.get(&"a"), Some(&"alice"));
727        assert_eq!(cache.get(&"b"), Some(&"bob"));
728        assert!(cache.contains_key(&"b"));
729        // counts: a -> 2, b -> 2, c -> 1
730
731        // "d" should not be admitted because its frequency is too low.
732        cache.insert("d", "david"); //   count: d -> 0
733        assert_eq!(cache.get(&"d"), None); //   d -> 1
734        assert!(!cache.contains_key(&"d"));
735
736        cache.insert("d", "david");
737        assert!(!cache.contains_key(&"d"));
738        assert_eq!(cache.get(&"d"), None); //   d -> 2
739
740        // "d" should be admitted and "c" should be evicted
741        // because d's frequency is higher than c's.
742        cache.insert("d", "dennis");
743        assert_eq!(cache.get(&"a"), Some(&"alice"));
744        assert_eq!(cache.get(&"b"), Some(&"bob"));
745        assert_eq!(cache.get(&"c"), None);
746        assert_eq!(cache.get(&"d"), Some(&"dennis"));
747        assert!(cache.contains_key(&"a"));
748        assert!(cache.contains_key(&"b"));
749        assert!(!cache.contains_key(&"c"));
750        assert!(cache.contains_key(&"d"));
751
752        cache.invalidate(&"b");
753        assert_eq!(cache.get(&"b"), None);
754        assert!(!cache.contains_key(&"b"));
755    }
756
757    #[test]
758    fn invalidate_all() {
759        let mut cache = Cache::new(100);
760        cache.enable_frequency_sketch_for_testing();
761
762        cache.insert("a", "alice");
763        cache.insert("b", "bob");
764        cache.insert("c", "cindy");
765        assert_eq!(cache.get(&"a"), Some(&"alice"));
766        assert_eq!(cache.get(&"b"), Some(&"bob"));
767        assert_eq!(cache.get(&"c"), Some(&"cindy"));
768        assert!(cache.contains_key(&"a"));
769        assert!(cache.contains_key(&"b"));
770        assert!(cache.contains_key(&"c"));
771
772        cache.invalidate_all();
773
774        cache.insert("d", "david");
775
776        assert!(cache.get(&"a").is_none());
777        assert!(cache.get(&"b").is_none());
778        assert!(cache.get(&"c").is_none());
779        assert_eq!(cache.get(&"d"), Some(&"david"));
780        assert!(!cache.contains_key(&"a"));
781        assert!(!cache.contains_key(&"b"));
782        assert!(!cache.contains_key(&"c"));
783        assert!(cache.contains_key(&"d"));
784    }
785
786    #[test]
787    fn invalidate_entries_if() {
788        use std::collections::HashSet;
789
790        let mut cache = Cache::new(100);
791        cache.enable_frequency_sketch_for_testing();
792
793        cache.insert(0, "alice");
794        cache.insert(1, "bob");
795        cache.insert(2, "alex");
796
797        assert_eq!(cache.get(&0), Some(&"alice"));
798        assert_eq!(cache.get(&1), Some(&"bob"));
799        assert_eq!(cache.get(&2), Some(&"alex"));
800        assert!(cache.contains_key(&0));
801        assert!(cache.contains_key(&1));
802        assert!(cache.contains_key(&2));
803
804        let names = ["alice", "alex"].iter().cloned().collect::<HashSet<_>>();
805        cache.invalidate_entries_if(move |_k, &v| names.contains(v));
806
807        cache.insert(3, "alice");
808
809        assert!(cache.get(&0).is_none());
810        assert!(cache.get(&2).is_none());
811        assert_eq!(cache.get(&1), Some(&"bob"));
812        // This should survive as it was inserted after calling invalidate_entries_if.
813        assert_eq!(cache.get(&3), Some(&"alice"));
814
815        assert!(!cache.contains_key(&0));
816        assert!(cache.contains_key(&1));
817        assert!(!cache.contains_key(&2));
818        assert!(cache.contains_key(&3));
819
820        assert_eq!(cache.cache.len(), 2);
821
822        cache.invalidate_entries_if(|_k, &v| v == "alice");
823        cache.invalidate_entries_if(|_k, &v| v == "bob");
824
825        assert!(cache.get(&1).is_none());
826        assert!(cache.get(&3).is_none());
827
828        assert!(!cache.contains_key(&1));
829        assert!(!cache.contains_key(&3));
830
831        assert_eq!(cache.cache.len(), 0);
832    }
833
834    #[cfg_attr(target_pointer_width = "16", ignore)]
835    #[test]
836    fn test_skt_capacity_will_not_overflow() {
837        // power of two
838        let pot = |exp| 2u64.pow(exp);
839
840        let ensure_sketch_len = |max_capacity, len, name| {
841            let mut cache = Cache::<u8, u8>::new(max_capacity);
842            cache.enable_frequency_sketch_for_testing();
843            assert_eq!(cache.frequency_sketch.table_len(), len as usize, "{}", name);
844        };
845
846        if cfg!(target_pointer_width = "32") {
847            let pot24 = pot(24);
848            let pot16 = pot(16);
849            ensure_sketch_len(0, 128, "0");
850            ensure_sketch_len(128, 128, "128");
851            ensure_sketch_len(pot16, pot16, "pot16");
852            // due to ceiling to next_power_of_two
853            ensure_sketch_len(pot16 + 1, pot(17), "pot16 + 1");
854            // due to ceiling to next_power_of_two
855            ensure_sketch_len(pot24 - 1, pot24, "pot24 - 1");
856            ensure_sketch_len(pot24, pot24, "pot24");
857            ensure_sketch_len(pot(27), pot24, "pot(27)");
858            ensure_sketch_len(u32::MAX as u64, pot24, "u32::MAX");
859        } else {
860            // target_pointer_width: 64 or larger.
861            let pot30 = pot(30);
862            let pot16 = pot(16);
863            ensure_sketch_len(0, 128, "0");
864            ensure_sketch_len(128, 128, "128");
865            ensure_sketch_len(pot16, pot16, "pot16");
866            // due to ceiling to next_power_of_two
867            ensure_sketch_len(pot16 + 1, pot(17), "pot16 + 1");
868
869            // The following tests will allocate large memory (~8GiB).
870            // Skip when running on Circle CI.
871            if !cfg!(circleci) {
872                // due to ceiling to next_power_of_two
873                ensure_sketch_len(pot30 - 1, pot30, "pot30- 1");
874                ensure_sketch_len(pot30, pot30, "pot30");
875                ensure_sketch_len(u64::MAX, pot30, "u64::MAX");
876            }
877        };
878    }
879
880    #[test]
881    fn remove_decrements_entry_count() {
882        let mut cache = Cache::new(3);
883        cache.insert("a", "alice");
884        cache.insert("b", "bob");
885        assert_eq!(cache.entry_count(), 2);
886
887        let removed = cache.remove(&"a");
888        assert_eq!(removed, Some("alice"));
889        assert_eq!(cache.entry_count(), 1);
890
891        cache.remove(&"nonexistent");
892        assert_eq!(cache.entry_count(), 1);
893
894        cache.remove(&"b");
895        assert_eq!(cache.entry_count(), 0);
896    }
897
898    #[test]
899    fn invalidate_decrements_entry_count() {
900        let mut cache = Cache::new(3);
901        cache.insert("a", "alice");
902        cache.insert("b", "bob");
903        assert_eq!(cache.entry_count(), 2);
904
905        cache.invalidate(&"a");
906        assert_eq!(cache.entry_count(), 1);
907
908        cache.invalidate(&"nonexistent");
909        assert_eq!(cache.entry_count(), 1);
910
911        cache.invalidate(&"b");
912        assert_eq!(cache.entry_count(), 0);
913    }
914
915    #[test]
916    fn insert_after_remove_on_full_cache() {
917        let mut cache = Cache::new(2);
918        cache.insert("a", "alice");
919        cache.insert("b", "bob");
920        assert_eq!(cache.entry_count(), 2);
921
922        cache.remove(&"a");
923        assert_eq!(cache.entry_count(), 1);
924
925        cache.insert("c", "cindy");
926        assert_eq!(cache.entry_count(), 2);
927        assert_eq!(cache.get(&"c"), Some(&"cindy"));
928        assert_eq!(cache.get(&"b"), Some(&"bob"));
929        assert_eq!(cache.get(&"a"), None);
930    }
931
932    #[test]
933    fn insert_after_invalidate_on_full_cache() {
934        let mut cache = Cache::new(2);
935        cache.insert("a", "alice");
936        cache.insert("b", "bob");
937        assert_eq!(cache.entry_count(), 2);
938
939        cache.invalidate(&"a");
940        assert_eq!(cache.entry_count(), 1);
941
942        cache.insert("c", "cindy");
943        assert_eq!(cache.entry_count(), 2);
944        assert_eq!(cache.get(&"c"), Some(&"cindy"));
945        assert_eq!(cache.get(&"b"), Some(&"bob"));
946        assert_eq!(cache.get(&"a"), None);
947    }
948
949    #[test]
950    fn invalidate_all_panic_safety() {
951        use std::panic::catch_unwind;
952        use std::panic::AssertUnwindSafe;
953        use std::sync::atomic::{AtomicU32, Ordering};
954
955        static DROP_COUNT: AtomicU32 = AtomicU32::new(0);
956
957        struct PanicOnDrop {
958            id: u32,
959            should_panic: bool,
960        }
961
962        impl Drop for PanicOnDrop {
963            fn drop(&mut self) {
964                DROP_COUNT.fetch_add(1, Ordering::Relaxed);
965                if self.should_panic {
966                    panic!("intentional panic in drop for id={}", self.id);
967                }
968            }
969        }
970
971        DROP_COUNT.store(0, Ordering::Relaxed);
972        let mut cache = Cache::new(10);
973        cache.insert(
974            1,
975            PanicOnDrop {
976                id: 1,
977                should_panic: false,
978            },
979        );
980        cache.insert(
981            2,
982            PanicOnDrop {
983                id: 2,
984                should_panic: true,
985            },
986        );
987        cache.insert(
988            3,
989            PanicOnDrop {
990                id: 3,
991                should_panic: false,
992            },
993        );
994        assert_eq!(cache.entry_count(), 3);
995
996        let result = catch_unwind(AssertUnwindSafe(|| {
997            cache.invalidate_all();
998        }));
999        assert!(result.is_err());
1000
1001        assert_eq!(cache.entry_count(), 0);
1002        assert_eq!(cache.cache.len(), 0);
1003
1004        cache.insert(
1005            4,
1006            PanicOnDrop {
1007                id: 4,
1008                should_panic: false,
1009            },
1010        );
1011        assert_eq!(cache.entry_count(), 1);
1012        assert!(cache.contains_key(&4));
1013    }
1014
1015    #[test]
1016    fn test_debug_format() {
1017        let mut cache = Cache::new(10);
1018        cache.insert('a', "alice");
1019        cache.insert('b', "bob");
1020        cache.insert('c', "cindy");
1021
1022        let debug_str = format!("{:?}", cache);
1023        assert!(debug_str.starts_with('{'));
1024        assert!(debug_str.contains(r#"'a': "alice""#));
1025        assert!(debug_str.contains(r#"'b': "bob""#));
1026        assert!(debug_str.contains(r#"'c': "cindy""#));
1027        assert!(debug_str.ends_with('}'));
1028    }
1029}