Skip to main content

micro_moka/unsync/
cache.rs

1use super::{CacheBuilder, IndexDeque, Iter, Slab, SlabEntry, SENTINEL};
2use crate::Policy;
3
4use hashbrown::HashTable;
5use std::{
6    borrow::Borrow,
7    collections::hash_map::RandomState,
8    fmt,
9    hash::{BuildHasher, Hash},
10};
11
12/// An in-memory cache that is _not_ thread-safe.
13///
14/// `Cache` utilizes a hash table [`hashbrown::HashTable`][hb-hashtable] for the
15/// central key-value storage. `Cache` performs a best-effort bounding of the
16/// map using an entry replacement algorithm to determine which entries to evict
17/// when the capacity is exceeded.
18///
19/// [hb-hashtable]: https://docs.rs/hashbrown/latest/hashbrown/struct.HashTable.html
20///
21/// # Characteristic difference between `unsync` and `sync`/`future` caches
22///
23/// If you use a cache from a single thread application, `unsync::Cache` may
24/// outperform other caches for updates and retrievals because other caches have some
25/// overhead on syncing internal data structures between threads.
26///
27/// # Examples
28///
29/// Cache entries are manually added using the insert method, and are stored in the
30/// cache until either evicted or manually invalidated.
31///
32/// Here's an example of reading and updating a cache by using the main thread:
33///
34///```rust
35/// use micro_moka::unsync::Cache;
36///
37/// const NUM_KEYS: usize = 64;
38///
39/// fn value(n: usize) -> String {
40///     format!("value {}", n)
41/// }
42///
43/// // Create a cache that can store up to 10,000 entries.
44/// let mut cache = Cache::new(10_000);
45///
46/// // Insert 64 entries.
47/// for key in 0..NUM_KEYS {
48///     cache.insert(key, value(key));
49/// }
50///
51/// // Invalidate every 4 element of the inserted entries.
52/// for key in (0..NUM_KEYS).step_by(4) {
53///     cache.invalidate(&key);
54/// }
55///
56/// // Verify the result.
57/// for key in 0..NUM_KEYS {
58///     if key % 4 == 0 {
59///         assert_eq!(cache.get(&key), None);
60///     } else {
61///         assert_eq!(cache.get(&key), Some(&value(key)));
62///     }
63/// }
64/// ```
65///
66/// # Hashing Algorithm
67///
68/// By default, `Cache` uses a hashing algorithm selected to provide resistance
69/// against HashDoS attacks. It will the same one used by
70/// `std::collections::HashMap`, which is currently SipHash 1-3.
71///
72/// While SipHash's performance is very competitive for medium sized keys, other
73/// hashing algorithms will outperform it for small keys such as integers as well as
74/// large keys such as long strings. However those algorithms will typically not
75/// protect against attacks such as HashDoS.
76///
77/// The hashing algorithm can be replaced on a per-`Cache` basis using the
78/// [`build_with_hasher`][build-with-hasher-method] method of the
79/// `CacheBuilder`. Many alternative algorithms are available on crates.io, such
80/// as the [aHash][ahash-crate] crate.
81///
82/// [build-with-hasher-method]: ./struct.CacheBuilder.html#method.build_with_hasher
83/// [ahash-crate]: https://crates.io/crates/ahash
84///
85pub struct Cache<K, V, S = RandomState> {
86    max_capacity: Option<u64>,
87    entry_count: u64,
88    table: HashTable<u32>,
89    build_hasher: S,
90    slab: Slab<K, V>,
91    deque: IndexDeque,
92}
93
94impl<K, V, S> fmt::Debug for Cache<K, V, S>
95where
96    K: fmt::Debug + Eq + Hash,
97    V: fmt::Debug,
98    S: BuildHasher + Clone,
99{
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        let mut d_map = f.debug_map();
102
103        for (k, v) in self.iter() {
104            d_map.entry(&k, &v);
105        }
106
107        d_map.finish()
108    }
109}
110
111impl<K, V> Cache<K, V, RandomState>
112where
113    K: Hash + Eq,
114{
115    /// Constructs a new `Cache<K, V>` that will store up to the `max_capacity` entries.
116    ///
117    /// To adjust various configuration knobs such as `initial_capacity`, use the
118    /// [`CacheBuilder`][builder-struct].
119    ///
120    /// [builder-struct]: ./struct.CacheBuilder.html
121    pub fn new(max_capacity: u64) -> Self {
122        let build_hasher = RandomState::default();
123        Self::with_everything(Some(max_capacity), None, build_hasher)
124    }
125
126    /// Returns a [`CacheBuilder`][builder-struct], which can builds a `Cache` with
127    /// various configuration knobs.
128    ///
129    /// [builder-struct]: ./struct.CacheBuilder.html
130    pub fn builder() -> CacheBuilder<K, V, Cache<K, V, RandomState>> {
131        CacheBuilder::default()
132    }
133}
134
135//
136// public
137//
138impl<K, V, S> Cache<K, V, S> {
139    /// Returns a read-only cache policy of this cache.
140    ///
141    /// At this time, cache policy cannot be modified after cache creation.
142    /// A future version may support to modify it.
143    pub fn policy(&self) -> Policy {
144        Policy::new(self.max_capacity)
145    }
146
147    /// Returns the number of entries in this cache.
148    ///
149    /// # Example
150    ///
151    /// ```rust
152    /// use micro_moka::unsync::Cache;
153    ///
154    /// let mut cache = Cache::new(10);
155    /// cache.insert('n', "Netherland Dwarf");
156    /// cache.insert('l', "Lop Eared");
157    /// cache.insert('d', "Dutch");
158    ///
159    /// // Ensure an entry exists.
160    /// assert!(cache.contains_key(&'n'));
161    ///
162    /// // Followings will print the actual numbers.
163    /// println!("{}", cache.entry_count());   // -> 3
164    /// ```
165    ///
166    pub fn entry_count(&self) -> u64 {
167        self.entry_count
168    }
169
170    /// Returns the total weighted size of entries in this cache.
171    ///
172    /// This is equivalent to `entry_count` as weight support has been removed.
173    pub fn weighted_size(&self) -> u64 {
174        self.entry_count
175    }
176}
177
178impl<K, V, S> Cache<K, V, S>
179where
180    K: Hash + Eq,
181    S: BuildHasher + Clone,
182{
183    pub(crate) fn with_everything(
184        max_capacity: Option<u64>,
185        initial_capacity: Option<usize>,
186        build_hasher: S,
187    ) -> Self {
188        let init_cap = initial_capacity.unwrap_or_default();
189
190        Self {
191            max_capacity,
192            entry_count: 0,
193            table: HashTable::with_capacity(init_cap),
194            build_hasher,
195            slab: if init_cap > 0 {
196                Slab::with_capacity(init_cap)
197            } else {
198                Slab::new()
199            },
200            deque: IndexDeque::default(),
201        }
202    }
203
204    /// Returns `true` if the cache contains a value for the key.
205    ///
206    /// Unlike the `get` method, this method is not considered a cache read operation,
207    /// so it does not mark the entry as visited.
208    ///
209    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
210    /// on the borrowed form _must_ match those for the key type.
211    #[inline]
212    pub fn contains_key<Q>(&self, key: &Q) -> bool
213    where
214        K: Borrow<Q>,
215        Q: Hash + Eq + ?Sized,
216    {
217        let hash = self.hash(key);
218        self.table
219            .find(hash, |&idx| self.slab.get(idx).key.borrow() == key)
220            .is_some()
221    }
222
223    /// Returns an immutable reference of the value corresponding to the key.
224    ///
225    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
226    /// on the borrowed form _must_ match those for the key type.
227    #[inline]
228    pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
229    where
230        K: Borrow<Q>,
231        Q: Hash + Eq + ?Sized,
232    {
233        let hash = self.hash(key);
234
235        let idx = match self
236            .table
237            .find(hash, |&idx| self.slab.get(idx).key.borrow() == key)
238        {
239            Some(&idx) => idx,
240            None => return None,
241        };
242
243        self.slab.get_mut(idx).visited = true;
244        Some(&self.slab.get(idx).value)
245    }
246
247    /// Returns an immutable reference of the value corresponding to the key,
248    /// without marking the entry as visited.
249    ///
250    /// Unlike [`get`](#method.get), this method does not count as a cache read
251    /// for eviction purposes: the entry's visited bit is not set. This is useful
252    /// when you want to inspect the cache without influencing which entries get
253    /// evicted, or when you only have a shared (`&self`) reference.
254    ///
255    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
256    /// on the borrowed form _must_ match those for the key type.
257    ///
258    /// # Example
259    ///
260    /// ```rust
261    /// use micro_moka::unsync::Cache;
262    ///
263    /// let mut cache = Cache::new(100);
264    /// cache.insert("a", "alice");
265    ///
266    /// // peek() returns the value without affecting eviction order.
267    /// assert_eq!(cache.peek(&"a"), Some(&"alice"));
268    /// assert_eq!(cache.peek(&"missing"), None);
269    /// ```
270    #[inline]
271    pub fn peek<Q>(&self, key: &Q) -> Option<&V>
272    where
273        K: Borrow<Q>,
274        Q: Hash + Eq + ?Sized,
275    {
276        let hash = self.hash(key);
277        let idx = self
278            .table
279            .find(hash, |&idx| self.slab.get(idx).key.borrow() == key)?;
280        Some(&self.slab.get(*idx).value)
281    }
282
283    /// Returns a reference to the value corresponding to the key, computing and
284    /// inserting it if not present.
285    ///
286    /// If the cache contains the key, the existing value is returned and the
287    /// entry is marked as visited. If the key is absent, `f` is called to
288    /// compute the value, which is then inserted and returned.
289    ///
290    /// This performs a single hash computation and a single probe sequence,
291    /// making it more efficient than a `get()` followed by `insert()`.
292    ///
293    /// # Example
294    ///
295    /// ```rust
296    /// use micro_moka::unsync::Cache;
297    ///
298    /// let mut cache = Cache::new(100);
299    ///
300    /// let value = cache.get_or_insert_with("key", || "computed".to_string());
301    /// assert_eq!(value, "computed");
302    ///
303    /// // Second call returns the cached value without calling the closure.
304    /// let value = cache.get_or_insert_with("key", || panic!("should not be called"));
305    /// assert_eq!(value, "computed");
306    /// ```
307    #[inline]
308    pub fn get_or_insert_with<F>(&mut self, key: K, f: F) -> &V
309    where
310        F: FnOnce() -> V,
311    {
312        let hash = self.hash(&key);
313
314        if let Some(&idx) = self
315            .table
316            .find(hash, |&idx| self.slab.get(idx).key.borrow() == &key)
317        {
318            self.slab.get_mut(idx).visited = true;
319            return &self.slab.get(idx).value;
320        }
321
322        let value = f();
323
324        if !self.has_enough_capacity(1, self.entry_count) {
325            self.sieve_evict_one();
326        }
327
328        let slab_entry = SlabEntry {
329            key,
330            value,
331            hash,
332            visited: false,
333            prev: SENTINEL,
334            next: SENTINEL,
335        };
336        let idx = self.slab.allocate(slab_entry);
337
338        let slab = &self.slab;
339        self.table
340            .insert_unique(hash, idx, |&existing_idx| slab.get(existing_idx).hash);
341
342        self.deque.push_back(&mut self.slab, idx);
343        self.entry_count += 1;
344
345        &self.slab.get(idx).value
346    }
347
348    /// Inserts a key-value pair into the cache.
349    ///
350    /// If the cache has this key present, the value is updated.
351    #[inline]
352    pub fn insert(&mut self, key: K, value: V) {
353        let hash = self.hash(&key);
354
355        if let Some(&idx) = self
356            .table
357            .find(hash, |&idx| self.slab.get(idx).key.borrow() == &key)
358        {
359            let entry = self.slab.get_mut(idx);
360            entry.value = value;
361            entry.visited = true;
362            return;
363        }
364
365        if !self.has_enough_capacity(1, self.entry_count) {
366            if self.max_capacity == Some(0) {
367                return;
368            }
369
370            self.sieve_evict_one();
371        }
372
373        let slab_entry = SlabEntry {
374            key,
375            value,
376            hash,
377            visited: false,
378            prev: SENTINEL,
379            next: SENTINEL,
380        };
381        let idx = self.slab.allocate(slab_entry);
382
383        let slab = &self.slab;
384        self.table
385            .insert_unique(hash, idx, |&existing_idx| slab.get(existing_idx).hash);
386
387        self.deque.push_back(&mut self.slab, idx);
388        self.entry_count += 1;
389    }
390
391    /// Discards any cached value for the key.
392    ///
393    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
394    /// on the borrowed form _must_ match those for the key type.
395    #[inline]
396    pub fn invalidate<Q>(&mut self, key: &Q)
397    where
398        K: Borrow<Q>,
399        Q: Hash + Eq + ?Sized,
400    {
401        let hash = self.hash(key);
402        let slab = &self.slab;
403        if let Ok(entry) = self
404            .table
405            .find_entry(hash, |&idx| slab.get(idx).key.borrow() == key)
406        {
407            let (idx, _) = entry.remove();
408            self.deque.advance_hand_past(&self.slab, idx);
409            self.deque.unlink(&mut self.slab, idx);
410            self.slab.deallocate(idx);
411            self.entry_count -= 1;
412        }
413    }
414
415    /// Discards any cached value for the key, returning the cached value.
416    ///
417    /// The key may be any borrowed form of the cache's key type, but `Hash` and `Eq`
418    /// on the borrowed form _must_ match those for the key type.
419    #[inline]
420    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
421    where
422        K: Borrow<Q>,
423        Q: Hash + Eq + ?Sized,
424    {
425        let hash = self.hash(key);
426        let slab = &self.slab;
427        if let Ok(entry) = self
428            .table
429            .find_entry(hash, |&idx| slab.get(idx).key.borrow() == key)
430        {
431            let (idx, _) = entry.remove();
432            self.deque.advance_hand_past(&self.slab, idx);
433            self.deque.unlink(&mut self.slab, idx);
434            let slab_entry = self.slab.deallocate(idx);
435            self.entry_count -= 1;
436            Some(slab_entry.value)
437        } else {
438            None
439        }
440    }
441
442    /// Discards all cached values.
443    #[cold]
444    #[inline(never)]
445    pub fn invalidate_all(&mut self) {
446        let old_capacity = self.table.capacity();
447        let old_table = std::mem::replace(&mut self.table, HashTable::new());
448        let old_slab = std::mem::replace(&mut self.slab, Slab::new());
449        self.deque.clear();
450        self.entry_count = 0;
451
452        drop(old_table);
453        drop(old_slab);
454
455        self.table.reserve(old_capacity, |&idx| {
456            // This closure is for rehashing during reserve. Since the table is
457            // empty after the swap, this will never be called, but we must
458            // provide it.
459            let _ = idx;
460            0
461        });
462    }
463
464    /// Discards cached values that satisfy a predicate.
465    ///
466    /// `invalidate_entries_if` takes a closure that returns `true` or `false`.
467    /// `invalidate_entries_if` will apply the closure to each cached value,
468    /// and if the closure returns `true`, the value will be invalidated.
469    #[cold]
470    #[inline(never)]
471    pub fn invalidate_entries_if(&mut self, mut predicate: impl FnMut(&K, &V) -> bool) {
472        let indices_to_invalidate: Vec<u32> = self
473            .slab
474            .iter()
475            .filter(|(_, entry)| predicate(&entry.key, &entry.value))
476            .map(|(idx, _)| idx)
477            .collect();
478
479        let mut invalidated = 0u64;
480        for idx in indices_to_invalidate {
481            let hash = self.slab.get(idx).hash;
482            if let Ok(entry) = self.table.find_entry(hash, |&table_idx| table_idx == idx) {
483                entry.remove();
484                self.deque.advance_hand_past(&self.slab, idx);
485                self.deque.unlink(&mut self.slab, idx);
486                self.slab.deallocate(idx);
487                invalidated += 1;
488            }
489        }
490        self.entry_count -= invalidated;
491    }
492
493    /// Creates an iterator visiting all key-value pairs in arbitrary order. The
494    /// iterator element type is `(&K, &V)`.
495    ///
496    /// Unlike the `get` method, visiting entries via an iterator do not mark
497    /// entries as visited for eviction purposes.
498    ///
499    /// # Examples
500    ///
501    /// ```rust
502    /// use micro_moka::unsync::Cache;
503    ///
504    /// let mut cache = Cache::new(100);
505    /// cache.insert("Julia", 14);
506    ///
507    /// let mut iter = cache.iter();
508    /// let (k, v) = iter.next().unwrap(); // (&K, &V)
509    /// assert_eq!(k, &"Julia");
510    /// assert_eq!(v, &14);
511    ///
512    /// assert!(iter.next().is_none());
513    /// ```
514    ///
515    pub fn iter(&self) -> Iter<'_, K, V> {
516        Iter::new(&self.slab.entries)
517    }
518}
519
520//
521// private
522//
523impl<K, V, S> Cache<K, V, S>
524where
525    K: Hash + Eq,
526    S: BuildHasher + Clone,
527{
528    #[inline]
529    fn hash<Q>(&self, key: &Q) -> u64
530    where
531        K: Borrow<Q>,
532        Q: Hash + Eq + ?Sized,
533    {
534        self.build_hasher.hash_one(key)
535    }
536
537    #[inline]
538    fn has_enough_capacity(&self, candidate_weight: u32, ws: u64) -> bool {
539        self.max_capacity
540            .map(|limit| ws + candidate_weight as u64 <= limit)
541            .unwrap_or(true)
542    }
543
544    #[cold]
545    #[inline(never)]
546    fn sieve_evict_one(&mut self) {
547        if let Some(victim_idx) = self.deque.sieve_evict(&mut self.slab) {
548            let victim_hash = self.slab.get(victim_idx).hash;
549            if let Ok(entry) = self
550                .table
551                .find_entry(victim_hash, |&table_idx| table_idx == victim_idx)
552            {
553                entry.remove();
554            }
555            self.slab.deallocate(victim_idx);
556            self.entry_count -= 1;
557        }
558    }
559}
560
561#[cfg(test)]
562mod tests {
563    use super::Cache;
564
565    #[test]
566    fn basic_single_thread() {
567        let mut cache = Cache::new(3);
568
569        cache.insert("a", "alice");
570        cache.insert("b", "bob");
571        assert_eq!(cache.get(&"a"), Some(&"alice"));
572        assert!(cache.contains_key(&"a"));
573        assert!(cache.contains_key(&"b"));
574        assert_eq!(cache.get(&"b"), Some(&"bob"));
575
576        cache.insert("c", "cindy");
577        assert_eq!(cache.get(&"c"), Some(&"cindy"));
578        assert!(cache.contains_key(&"c"));
579
580        assert!(cache.contains_key(&"a"));
581        assert_eq!(cache.get(&"a"), Some(&"alice"));
582        assert_eq!(cache.get(&"b"), Some(&"bob"));
583        assert!(cache.contains_key(&"b"));
584
585        // All entries are visited. SIEVE will clear visited bits during sweep
586        // and evict the first unvisited entry it finds.
587        cache.insert("d", "david");
588        assert_eq!(cache.entry_count(), 3);
589        assert!(cache.contains_key(&"d"));
590
591        cache.invalidate(&"b");
592        assert_eq!(cache.get(&"b"), None);
593        assert!(!cache.contains_key(&"b"));
594    }
595
596    #[test]
597    fn sieve_evicts_unvisited_entry() {
598        let mut cache = Cache::new(3);
599
600        cache.insert("a", "alice");
601        cache.insert("b", "bob");
602        cache.insert("c", "cindy");
603
604        // Visit only "b" and "c", leaving "a" unvisited (only inserted, never get'd).
605        cache.get(&"b");
606        cache.get(&"c");
607
608        // Insert "d": SIEVE sweeps from tail backward. "c" is visited (cleared),
609        // "b" is visited (cleared), "a" is unvisited -> evicted.
610        cache.insert("d", "david");
611        assert_eq!(cache.entry_count(), 3);
612        assert_eq!(cache.get(&"a"), None);
613        assert!(cache.contains_key(&"b"));
614        assert!(cache.contains_key(&"c"));
615        assert!(cache.contains_key(&"d"));
616    }
617
618    #[test]
619    fn sieve_visited_entries_get_second_chance() {
620        let mut cache = Cache::new(3);
621
622        cache.insert("a", "alice");
623        cache.insert("b", "bob");
624        cache.insert("c", "cindy");
625
626        // Visit all entries.
627        cache.get(&"a");
628        cache.get(&"b");
629        cache.get(&"c");
630
631        // Insert "d": SIEVE sweeps and clears all visited bits, then wraps
632        // around and evicts the first (now-unvisited) entry from the tail.
633        cache.insert("d", "david");
634        assert_eq!(cache.entry_count(), 3);
635        assert!(cache.contains_key(&"d"));
636    }
637
638    #[test]
639    fn sieve_new_entry_always_admitted() {
640        let mut cache = Cache::new(3);
641
642        cache.insert("a", "alice");
643        cache.insert("b", "bob");
644        cache.insert("c", "cindy");
645
646        // Unlike W-TinyLFU, SIEVE always admits new entries (no frequency check).
647        // Even with all existing entries heavily accessed, the new entry is admitted.
648        for _ in 0..10 {
649            cache.get(&"a");
650            cache.get(&"b");
651            cache.get(&"c");
652        }
653
654        cache.insert("d", "david");
655        assert_eq!(cache.entry_count(), 3);
656        assert!(cache.contains_key(&"d"));
657    }
658
659    #[test]
660    fn invalidate_all() {
661        let mut cache = Cache::new(100);
662
663        cache.insert("a", "alice");
664        cache.insert("b", "bob");
665        cache.insert("c", "cindy");
666        assert_eq!(cache.get(&"a"), Some(&"alice"));
667        assert_eq!(cache.get(&"b"), Some(&"bob"));
668        assert_eq!(cache.get(&"c"), Some(&"cindy"));
669        assert!(cache.contains_key(&"a"));
670        assert!(cache.contains_key(&"b"));
671        assert!(cache.contains_key(&"c"));
672
673        cache.invalidate_all();
674
675        cache.insert("d", "david");
676
677        assert!(cache.get(&"a").is_none());
678        assert!(cache.get(&"b").is_none());
679        assert!(cache.get(&"c").is_none());
680        assert_eq!(cache.get(&"d"), Some(&"david"));
681        assert!(!cache.contains_key(&"a"));
682        assert!(!cache.contains_key(&"b"));
683        assert!(!cache.contains_key(&"c"));
684        assert!(cache.contains_key(&"d"));
685    }
686
687    #[test]
688    fn invalidate_entries_if() {
689        use std::collections::HashSet;
690
691        let mut cache = Cache::new(100);
692
693        cache.insert(0, "alice");
694        cache.insert(1, "bob");
695        cache.insert(2, "alex");
696
697        assert_eq!(cache.get(&0), Some(&"alice"));
698        assert_eq!(cache.get(&1), Some(&"bob"));
699        assert_eq!(cache.get(&2), Some(&"alex"));
700        assert!(cache.contains_key(&0));
701        assert!(cache.contains_key(&1));
702        assert!(cache.contains_key(&2));
703
704        let names = ["alice", "alex"].iter().cloned().collect::<HashSet<_>>();
705        cache.invalidate_entries_if(move |_k, &v| names.contains(v));
706
707        cache.insert(3, "alice");
708
709        assert!(cache.get(&0).is_none());
710        assert!(cache.get(&2).is_none());
711        assert_eq!(cache.get(&1), Some(&"bob"));
712        assert_eq!(cache.get(&3), Some(&"alice"));
713
714        assert!(!cache.contains_key(&0));
715        assert!(cache.contains_key(&1));
716        assert!(!cache.contains_key(&2));
717        assert!(cache.contains_key(&3));
718
719        assert_eq!(cache.table.len(), 2);
720
721        cache.invalidate_entries_if(|_k, &v| v == "alice");
722        cache.invalidate_entries_if(|_k, &v| v == "bob");
723
724        assert!(cache.get(&1).is_none());
725        assert!(cache.get(&3).is_none());
726
727        assert!(!cache.contains_key(&1));
728        assert!(!cache.contains_key(&3));
729
730        assert_eq!(cache.table.len(), 0);
731    }
732
733    #[test]
734    fn remove_decrements_entry_count() {
735        let mut cache = Cache::new(3);
736        cache.insert("a", "alice");
737        cache.insert("b", "bob");
738        assert_eq!(cache.entry_count(), 2);
739
740        let removed = cache.remove(&"a");
741        assert_eq!(removed, Some("alice"));
742        assert_eq!(cache.entry_count(), 1);
743
744        cache.remove(&"nonexistent");
745        assert_eq!(cache.entry_count(), 1);
746
747        cache.remove(&"b");
748        assert_eq!(cache.entry_count(), 0);
749    }
750
751    #[test]
752    fn invalidate_decrements_entry_count() {
753        let mut cache = Cache::new(3);
754        cache.insert("a", "alice");
755        cache.insert("b", "bob");
756        assert_eq!(cache.entry_count(), 2);
757
758        cache.invalidate(&"a");
759        assert_eq!(cache.entry_count(), 1);
760
761        cache.invalidate(&"nonexistent");
762        assert_eq!(cache.entry_count(), 1);
763
764        cache.invalidate(&"b");
765        assert_eq!(cache.entry_count(), 0);
766    }
767
768    #[test]
769    fn insert_after_remove_on_full_cache() {
770        let mut cache = Cache::new(2);
771        cache.insert("a", "alice");
772        cache.insert("b", "bob");
773        assert_eq!(cache.entry_count(), 2);
774
775        cache.remove(&"a");
776        assert_eq!(cache.entry_count(), 1);
777
778        cache.insert("c", "cindy");
779        assert_eq!(cache.entry_count(), 2);
780        assert_eq!(cache.get(&"c"), Some(&"cindy"));
781        assert_eq!(cache.get(&"b"), Some(&"bob"));
782        assert_eq!(cache.get(&"a"), None);
783    }
784
785    #[test]
786    fn insert_after_invalidate_on_full_cache() {
787        let mut cache = Cache::new(2);
788        cache.insert("a", "alice");
789        cache.insert("b", "bob");
790        assert_eq!(cache.entry_count(), 2);
791
792        cache.invalidate(&"a");
793        assert_eq!(cache.entry_count(), 1);
794
795        cache.insert("c", "cindy");
796        assert_eq!(cache.entry_count(), 2);
797        assert_eq!(cache.get(&"c"), Some(&"cindy"));
798        assert_eq!(cache.get(&"b"), Some(&"bob"));
799        assert_eq!(cache.get(&"a"), None);
800    }
801
802    #[test]
803    fn invalidate_all_panic_safety() {
804        use std::panic::catch_unwind;
805        use std::panic::AssertUnwindSafe;
806        use std::sync::atomic::{AtomicU32, Ordering};
807
808        static DROP_COUNT: AtomicU32 = AtomicU32::new(0);
809
810        struct PanicOnDrop {
811            id: u32,
812            should_panic: bool,
813        }
814
815        impl Drop for PanicOnDrop {
816            fn drop(&mut self) {
817                DROP_COUNT.fetch_add(1, Ordering::Relaxed);
818                if self.should_panic {
819                    panic!("intentional panic in drop for id={}", self.id);
820                }
821            }
822        }
823
824        DROP_COUNT.store(0, Ordering::Relaxed);
825        let mut cache = Cache::new(10);
826        cache.insert(
827            1,
828            PanicOnDrop {
829                id: 1,
830                should_panic: false,
831            },
832        );
833        cache.insert(
834            2,
835            PanicOnDrop {
836                id: 2,
837                should_panic: true,
838            },
839        );
840        cache.insert(
841            3,
842            PanicOnDrop {
843                id: 3,
844                should_panic: false,
845            },
846        );
847        assert_eq!(cache.entry_count(), 3);
848
849        let result = catch_unwind(AssertUnwindSafe(|| {
850            cache.invalidate_all();
851        }));
852        assert!(result.is_err());
853
854        assert_eq!(cache.entry_count(), 0);
855        assert_eq!(cache.table.len(), 0);
856
857        cache.insert(
858            4,
859            PanicOnDrop {
860                id: 4,
861                should_panic: false,
862            },
863        );
864        assert_eq!(cache.entry_count(), 1);
865        assert!(cache.contains_key(&4));
866    }
867
868    #[test]
869    fn test_debug_format() {
870        let mut cache = Cache::new(10);
871        cache.insert('a', "alice");
872        cache.insert('b', "bob");
873        cache.insert('c', "cindy");
874
875        let debug_str = format!("{:?}", cache);
876        assert!(debug_str.starts_with('{'));
877        assert!(debug_str.contains(r#"'a': "alice""#));
878        assert!(debug_str.contains(r#"'b': "bob""#));
879        assert!(debug_str.contains(r#"'c': "cindy""#));
880        assert!(debug_str.ends_with('}'));
881    }
882
883    #[test]
884    fn sub_capacity_inserts_skip_eviction() {
885        let mut cache = Cache::new(10);
886        for i in 0u32..5 {
887            cache.insert(i, i * 10);
888        }
889        assert_eq!(cache.entry_count(), 5);
890        for i in 0u32..5 {
891            assert_eq!(cache.get(&i), Some(&(i * 10)));
892        }
893    }
894
895    #[test]
896    fn eviction_triggers_when_over_capacity() {
897        let mut cache = Cache::new(3);
898
899        cache.insert(1, "a");
900        cache.insert(2, "b");
901        cache.insert(3, "c");
902        assert_eq!(cache.entry_count(), 3);
903
904        cache.insert(4, "d");
905        assert!(cache.entry_count() <= 3);
906    }
907
908    #[test]
909    fn warmup_to_full_transition() {
910        let mut cache = Cache::new(4);
911
912        cache.insert(1, "a");
913        cache.insert(2, "b");
914        assert_eq!(cache.entry_count(), 2);
915
916        cache.insert(3, "c");
917        cache.insert(4, "d");
918        assert_eq!(cache.entry_count(), 4);
919
920        for _ in 0..5 {
921            cache.get(&1);
922            cache.get(&2);
923            cache.get(&3);
924            cache.get(&4);
925        }
926
927        cache.insert(5, "e");
928        assert!(cache.entry_count() <= 4);
929    }
930
931    #[test]
932    fn invalidate_and_remove_skip_eviction_below_capacity() {
933        let mut cache = Cache::new(10);
934        cache.insert(1, "a");
935        cache.insert(2, "b");
936        cache.insert(3, "c");
937        assert_eq!(cache.entry_count(), 3);
938
939        cache.invalidate(&1);
940        assert_eq!(cache.entry_count(), 2);
941
942        let val = cache.remove(&2);
943        assert_eq!(val, Some("b"));
944        assert_eq!(cache.entry_count(), 1);
945
946        assert_eq!(cache.get(&3), Some(&"c"));
947    }
948
949    #[test]
950    fn peek_returns_value() {
951        let mut cache = Cache::new(10);
952        cache.insert("a", "alice");
953        cache.insert("b", "bob");
954
955        assert_eq!(cache.peek(&"a"), Some(&"alice"));
956        assert_eq!(cache.peek(&"b"), Some(&"bob"));
957    }
958
959    #[test]
960    fn peek_returns_none_for_missing_key() {
961        let cache = Cache::<&str, &str>::new(10);
962        assert_eq!(cache.peek(&"missing"), None);
963    }
964
965    #[test]
966    fn peek_does_not_set_visited() {
967        let mut cache = Cache::new(3);
968
969        cache.insert("a", "alice");
970        cache.insert("b", "bob");
971        cache.insert("c", "cindy");
972
973        // peek() should NOT set the visited bit.
974        cache.peek(&"a");
975        cache.peek(&"b");
976        cache.peek(&"c");
977
978        // None of the entries are visited, so SIEVE evicts the first unvisited
979        // entry from the tail (insertion order: a, b, c; sweep starts at tail=c).
980        cache.insert("d", "david");
981
982        // One entry was evicted to make room for "d".
983        assert_eq!(cache.entry_count(), 3);
984        assert!(cache.contains_key(&"d"));
985    }
986
987    #[test]
988    fn contains_key_with_shared_reference() {
989        let mut cache = Cache::new(10);
990        cache.insert("a", 1);
991        cache.insert("b", 2);
992        cache.insert("c", 3);
993
994        let cache_ref: &Cache<&str, i32> = &cache;
995        assert!(cache_ref.contains_key(&"a"));
996        assert!(cache_ref.contains_key(&"b"));
997        assert!(cache_ref.contains_key(&"c"));
998        assert!(!cache_ref.contains_key(&"d"));
999    }
1000
1001    #[test]
1002    fn zero_capacity_insert_returns_immediately() {
1003        let mut cache = Cache::new(0);
1004        cache.insert("a", "alice");
1005        assert_eq!(cache.entry_count(), 0);
1006        assert!(!cache.contains_key(&"a"));
1007        assert_eq!(cache.get(&"a"), None);
1008    }
1009
1010    #[test]
1011    fn update_existing_key_sets_visited() {
1012        let mut cache = Cache::new(3);
1013
1014        cache.insert("a", "alice");
1015        cache.insert("b", "bob");
1016        cache.insert("c", "cindy");
1017
1018        // Update "a" with a new value. This should set visited=true but NOT
1019        // move it to the back (SIEVE maintains insertion order).
1020        cache.insert("a", "anna");
1021        assert_eq!(cache.get(&"a"), Some(&"anna"));
1022        assert_eq!(cache.entry_count(), 3);
1023
1024        // "b" and "c" are not visited. Insert "d" to trigger eviction.
1025        // SIEVE should skip "a" (visited) and evict an unvisited entry.
1026        cache.insert("d", "david");
1027        assert_eq!(cache.entry_count(), 3);
1028        assert!(cache.contains_key(&"a"));
1029        assert!(cache.contains_key(&"d"));
1030    }
1031
1032    #[test]
1033    fn sieve_hand_advances_across_evictions() {
1034        let mut cache = Cache::new(3);
1035
1036        cache.insert("a", "alice");
1037        cache.insert("b", "bob");
1038        cache.insert("c", "cindy");
1039
1040        // No entries visited. First eviction should evict from tail end.
1041        cache.insert("d", "david");
1042        assert_eq!(cache.entry_count(), 3);
1043
1044        // Insert another to trigger second eviction. Hand should have advanced.
1045        cache.insert("e", "eve");
1046        assert_eq!(cache.entry_count(), 3);
1047
1048        // Insert a third to trigger third eviction.
1049        cache.insert("f", "frank");
1050        assert_eq!(cache.entry_count(), 3);
1051    }
1052
1053    #[test]
1054    fn sieve_multiple_evictions_cycle() {
1055        let mut cache = Cache::new(2);
1056
1057        for i in 0u32..20 {
1058            cache.insert(i, i * 10);
1059            assert!(cache.entry_count() <= 2);
1060        }
1061
1062        // The last two inserted should be present.
1063        assert_eq!(cache.entry_count(), 2);
1064        assert!(cache.contains_key(&19));
1065        assert!(cache.contains_key(&18));
1066    }
1067
1068    #[test]
1069    fn update_below_capacity_no_eviction() {
1070        let mut cache = Cache::new(5);
1071
1072        cache.insert("a", "alice");
1073        cache.insert("b", "bob");
1074        cache.insert("c", "cindy");
1075        assert_eq!(cache.entry_count(), 3);
1076
1077        // Updating below capacity should not evict anything.
1078        cache.insert("b", "betty");
1079        assert_eq!(cache.entry_count(), 3);
1080        assert_eq!(cache.get(&"b"), Some(&"betty"));
1081        assert!(cache.contains_key(&"a"));
1082        assert!(cache.contains_key(&"c"));
1083    }
1084
1085    #[test]
1086    fn get_or_insert_with_basic() {
1087        let mut cache = Cache::new(10);
1088
1089        let v = cache.get_or_insert_with("a", || "alice");
1090        assert_eq!(v, &"alice");
1091        assert_eq!(cache.entry_count(), 1);
1092
1093        let v = cache.get_or_insert_with("a", || panic!("should not be called"));
1094        assert_eq!(v, &"alice");
1095        assert_eq!(cache.entry_count(), 1);
1096    }
1097
1098    #[test]
1099    fn get_or_insert_with_closure_called_only_on_miss() {
1100        let mut cache = Cache::new(10);
1101        let mut call_count = 0;
1102
1103        cache.get_or_insert_with("a", || {
1104            call_count += 1;
1105            "alice"
1106        });
1107        assert_eq!(call_count, 1);
1108
1109        cache.get_or_insert_with("a", || {
1110            call_count += 1;
1111            "anna"
1112        });
1113        assert_eq!(call_count, 1);
1114
1115        cache.get_or_insert_with("b", || {
1116            call_count += 1;
1117            "bob"
1118        });
1119        assert_eq!(call_count, 2);
1120    }
1121
1122    #[test]
1123    fn get_or_insert_with_eviction_at_capacity() {
1124        let mut cache = Cache::new(3);
1125
1126        cache.get_or_insert_with("a", || "alice");
1127        cache.get_or_insert_with("b", || "bob");
1128        cache.get_or_insert_with("c", || "cindy");
1129        assert_eq!(cache.entry_count(), 3);
1130
1131        cache.get_or_insert_with("d", || "david");
1132        assert_eq!(cache.entry_count(), 3);
1133        assert!(cache.contains_key(&"d"));
1134    }
1135
1136    #[test]
1137    fn get_or_insert_with_existing_key_no_closure() {
1138        let mut cache = Cache::new(10);
1139        cache.insert("a", "alice");
1140
1141        let v = cache.get_or_insert_with("a", || panic!("should not be called"));
1142        assert_eq!(v, &"alice");
1143        assert_eq!(cache.entry_count(), 1);
1144    }
1145
1146    #[test]
1147    fn get_or_insert_with_sets_visited_on_hit() {
1148        let mut cache = Cache::new(3);
1149
1150        cache.insert("a", "alice");
1151        cache.insert("b", "bob");
1152        cache.insert("c", "cindy");
1153
1154        // Hit via get_or_insert_with sets visited=true on "a".
1155        cache.get_or_insert_with("a", || panic!("should not be called"));
1156
1157        // Insert "d" triggers eviction. SIEVE should skip "a" (visited)
1158        // and evict an unvisited entry.
1159        cache.insert("d", "david");
1160        assert_eq!(cache.entry_count(), 3);
1161        assert!(cache.contains_key(&"a"));
1162        assert!(cache.contains_key(&"d"));
1163    }
1164
1165    #[test]
1166    fn get_or_insert_with_zero_capacity() {
1167        let mut cache = Cache::new(0);
1168
1169        // Zero-capacity cache still stores the value (we must return &V).
1170        let v = cache.get_or_insert_with("a", || "alice");
1171        assert_eq!(v, &"alice");
1172
1173        // Same key: found in cache, closure not called.
1174        let v = cache.get_or_insert_with("a", || panic!("should not be called"));
1175        assert_eq!(v, &"alice");
1176
1177        // Different key: evicts "a", stores "b".
1178        let v = cache.get_or_insert_with("b", || "bob");
1179        assert_eq!(v, &"bob");
1180        assert_eq!(cache.entry_count(), 1);
1181        assert!(!cache.contains_key(&"a"));
1182        assert!(cache.contains_key(&"b"));
1183    }
1184
1185    #[test]
1186    fn get_or_insert_with_after_invalidate() {
1187        let mut cache = Cache::new(10);
1188        cache.insert("a", "alice");
1189        cache.invalidate(&"a");
1190        assert_eq!(cache.entry_count(), 0);
1191
1192        let v = cache.get_or_insert_with("a", || "anna");
1193        assert_eq!(v, &"anna");
1194        assert_eq!(cache.entry_count(), 1);
1195    }
1196
1197    #[test]
1198    fn get_or_insert_with_after_remove() {
1199        let mut cache = Cache::new(10);
1200        cache.insert("a", "alice");
1201        let removed = cache.remove(&"a");
1202        assert_eq!(removed, Some("alice"));
1203
1204        let v = cache.get_or_insert_with("a", || "anna");
1205        assert_eq!(v, &"anna");
1206        assert_eq!(cache.entry_count(), 1);
1207    }
1208}