Skip to main content

quick_cache/
sync.rs

1use std::{
2    future::Future,
3    hash::{BuildHasher, Hash},
4    hint::unreachable_unchecked,
5    time::Duration,
6};
7
8use crate::{
9    linked_slab::Token,
10    options::{Options, OptionsBuilder},
11    shard::{CacheShard, InsertStrategy},
12    shim::rw_lock::RwLock,
13    sync_placeholder::SharedPlaceholder,
14    DefaultHashBuilder, Equivalent, Lifecycle, MemoryUsed, UnitWeighter, Weighter,
15};
16
17use crate::shard::EntryOrPlaceholder;
18pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard};
19use crate::sync_placeholder::{JoinFuture, JoinResult};
20
21/// Error returned by non-blocking cache operations that do not consume their
22/// inputs when the relevant shard lock could not be acquired immediately.
23///
24/// This is used by borrowed-key/read-path operations. Non-blocking operations
25/// that consume owned inputs (e.g. `try_insert`) instead return those inputs
26/// on contention so the caller can retry or discard without losing data.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct LockContention;
29
30impl std::fmt::Display for LockContention {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        write!(f, "Lock Contention")
33    }
34}
35
36impl std::error::Error for LockContention {}
37
38/// A concurrent cache
39///
40/// The concurrent cache is internally composed of equally sized shards, each of which is independently
41/// synchronized. This allows for low contention when multiple threads are accessing the cache but limits the
42/// maximum weight capacity of each shard.
43///
44/// # Value
45/// Cache values are cloned when fetched. Users should wrap their values with `Arc<_>`
46/// if necessary to avoid expensive clone operations. If interior mutability is required
47/// `Arc<Mutex<_>>` or `Arc<RwLock<_>>` can also be used.
48///
49/// # Thread Safety and Concurrency
50/// The cache instance can be wrapped with an `Arc` (or equivalent) and shared between threads.
51/// All methods are accessible via non-mut references so no further synchronization (e.g. Mutex) is needed.
52pub struct Cache<
53    Key,
54    Val,
55    We = UnitWeighter,
56    B = DefaultHashBuilder,
57    L = DefaultLifecycle<Key, Val>,
58> {
59    hash_builder: B,
60    shards: Box<[RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>]>,
61    shards_mask: u64,
62}
63
64impl<Key: Eq + Hash, Val: Clone> Cache<Key, Val> {
65    /// Creates a new cache with holds up to `items_capacity` items (approximately).
66    pub fn new(items_capacity: usize) -> Self {
67        Self::with(
68            items_capacity,
69            items_capacity as u64,
70            Default::default(),
71            Default::default(),
72            Default::default(),
73        )
74    }
75}
76
77impl<Key: Eq + Hash, Val: Clone, We: Weighter<Key, Val> + Clone> Cache<Key, Val, We> {
78    pub fn with_weighter(
79        estimated_items_capacity: usize,
80        weight_capacity: u64,
81        weighter: We,
82    ) -> Self {
83        Self::with(
84            estimated_items_capacity,
85            weight_capacity,
86            weighter,
87            Default::default(),
88            Default::default(),
89        )
90    }
91}
92
93impl<
94        Key: Eq + Hash,
95        Val: Clone,
96        We: Weighter<Key, Val> + Clone,
97        B: BuildHasher + Clone,
98        L: Lifecycle<Key, Val> + Clone,
99    > Cache<Key, Val, We, B, L>
100{
101    /// Creates a new cache that can hold up to `weight_capacity` in weight.
102    /// `estimated_items_capacity` is the estimated number of items the cache is expected to hold,
103    /// roughly equivalent to `weight_capacity / average item weight`.
104    pub fn with(
105        estimated_items_capacity: usize,
106        weight_capacity: u64,
107        weighter: We,
108        hash_builder: B,
109        lifecycle: L,
110    ) -> Self {
111        Self::with_options(
112            OptionsBuilder::new()
113                .estimated_items_capacity(estimated_items_capacity)
114                .weight_capacity(weight_capacity)
115                .build()
116                .unwrap(),
117            weighter,
118            hash_builder,
119            lifecycle,
120        )
121    }
122
123    /// Constructs a cache based on [OptionsBuilder].
124    ///
125    /// # Example
126    ///
127    /// ```rust
128    /// use quick_cache::{sync::{Cache, DefaultLifecycle}, OptionsBuilder, UnitWeighter, DefaultHashBuilder};
129    ///
130    /// Cache::<(String, u64), String>::with_options(
131    ///   OptionsBuilder::new()
132    ///     .estimated_items_capacity(10000)
133    ///     .weight_capacity(10000)
134    ///     .build()
135    ///     .unwrap(),
136    ///     UnitWeighter,
137    ///     DefaultHashBuilder::default(),
138    ///     DefaultLifecycle::default(),
139    /// );
140    /// ```
141    pub fn with_options(options: Options, weighter: We, hash_builder: B, lifecycle: L) -> Self {
142        let mut num_shards = options.shards.next_power_of_two() as u64;
143        let estimated_items_capacity = options.estimated_items_capacity as u64;
144        let weight_capacity = options.weight_capacity;
145        let mut shard_items_cap =
146            estimated_items_capacity.saturating_add(num_shards - 1) / num_shards;
147        let mut shard_weight_cap =
148            options.weight_capacity.saturating_add(num_shards - 1) / num_shards;
149        // try to make each shard hold at least 32 items
150        while shard_items_cap < 32 && num_shards > 1 {
151            num_shards /= 2;
152            shard_items_cap = estimated_items_capacity.saturating_add(num_shards - 1) / num_shards;
153            shard_weight_cap = weight_capacity.saturating_add(num_shards - 1) / num_shards;
154        }
155        let shards = (0..num_shards)
156            .map(|_| {
157                RwLock::new(CacheShard::new(
158                    options.hot_allocation,
159                    options.ghost_allocation,
160                    shard_items_cap as usize,
161                    shard_weight_cap,
162                    weighter.clone(),
163                    hash_builder.clone(),
164                    lifecycle.clone(),
165                ))
166            })
167            .collect::<Vec<_>>();
168        Self {
169            shards: shards.into_boxed_slice(),
170            hash_builder,
171            shards_mask: num_shards - 1,
172        }
173    }
174
175    #[cfg(fuzzing)]
176    pub fn validate(&self) {
177        for s in &*self.shards {
178            s.read().validate(false)
179        }
180    }
181
182    /// Returns whether the cache is empty
183    pub fn is_empty(&self) -> bool {
184        self.shards.iter().all(|s| s.read().len() == 0)
185    }
186
187    /// Returns the number of cached items
188    pub fn len(&self) -> usize {
189        self.shards.iter().map(|s| s.read().len()).sum()
190    }
191
192    /// Returns the total weight of cached items
193    pub fn weight(&self) -> u64 {
194        self.shards.iter().map(|s| s.read().weight()).sum()
195    }
196
197    /// Returns the _total_ maximum weight capacity of cached items.
198    /// Note that the cache may be composed of multiple shards and each shard has its own maximum weight capacity,
199    /// see [`Self::shard_capacity`].
200    pub fn capacity(&self) -> u64 {
201        self.shards.iter().map(|s| s.read().capacity()).sum()
202    }
203
204    /// Returns the maximum weight capacity of each shard.
205    pub fn shard_capacity(&self) -> u64 {
206        self.shards[0].read().capacity()
207    }
208
209    /// Returns the number of shards.
210    pub fn num_shards(&self) -> usize {
211        self.shards.len()
212    }
213
214    /// Returns the number of misses
215    #[cfg(feature = "stats")]
216    pub fn misses(&self) -> u64 {
217        self.shards.iter().map(|s| s.read().misses()).sum()
218    }
219
220    /// Returns the number of hits
221    #[cfg(feature = "stats")]
222    pub fn hits(&self) -> u64 {
223        self.shards.iter().map(|s| s.read().hits()).sum()
224    }
225
226    #[inline]
227    fn compute_shard_index(&self, hash: u64) -> u64 {
228        // Give preference to the bits in the middle of the hash. When choosing the
229        // shard, rotate the hash by usize::BITS / 2 so we avoid the lower bits and
230        // the highest 7 bits that hashbrown uses internally for probing, improving
231        // the real entropy available to each hashbrown shard.
232        //
233        // The rotation is deliberately tied to usize::BITS, not u64::BITS: on 32-bit
234        // targets a usize-width hasher (e.g. FxHash) leaves the top 32 hash bits zero
235        // (hashbrown guards against this too; see its h1 / Tag::full), so we keep the
236        // shard window in the low word where the entropy actually lives. Rotating by
237        // u64::BITS / 2 there would send every key to shard 0.
238        hash.rotate_right(usize::BITS / 2) & self.shards_mask
239    }
240
241    /// Returns the shard index for the given key.
242    ///
243    /// The returned index is guaranteed to be in `[0, num_shards())`.
244    ///
245    /// # Use cases
246    ///
247    /// - **Batching**: group keys by shard index before acquiring shard locks, so
248    ///   each lock is taken only once per batch instead of once per key.
249    ///
250    /// # Notes
251    ///
252    /// The mapping from key to shard index depends on the [`BuildHasher`] supplied
253    /// at construction time. If two `Cache` instances are built with different
254    /// hashers, the same key may map to different shard indices.
255    ///
256    /// [`BuildHasher`]: std::hash::BuildHasher
257    #[inline]
258    pub fn shard_index<Q: Hash + Equivalent<Key> + ?Sized>(&self, key: &Q) -> usize {
259        let hash = self.hash_builder.hash_one(key);
260        self.compute_shard_index(hash) as usize
261    }
262
263    #[inline]
264    fn shard_for<Q>(
265        &self,
266        key: &Q,
267    ) -> Option<(
268        &RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>,
269        u64,
270    )>
271    where
272        Q: Hash + Equivalent<Key> + ?Sized,
273    {
274        let hash = self.hash_builder.hash_one(key);
275        let shard_idx = self.compute_shard_index(hash) as usize;
276        self.shards.get(shard_idx).map(|s| (s, hash))
277    }
278
279    /// Reserve additional space for `additional` entries.
280    /// Note that this is counted in entries, and is not weighted.
281    pub fn reserve(&self, additional: usize) {
282        let additional_per_shard =
283            additional.saturating_add(self.shards.len() - 1) / self.shards.len();
284        for s in &*self.shards {
285            s.write().reserve(additional_per_shard);
286        }
287    }
288
289    /// Checks if a key exists in the cache.
290    pub fn contains_key<Q>(&self, key: &Q) -> bool
291    where
292        Q: Hash + Equivalent<Key> + ?Sized,
293    {
294        self.shard_for(key)
295            .is_some_and(|(shard, hash)| shard.read().contains(hash, key))
296    }
297
298    /// Attempts to check if a key exists in the cache without blocking.
299    /// Returns `Ok(true)` if present, `Ok(false)` if absent,
300    /// or `Err(LockContention)` if the shard lock could not be acquired without blocking.
301    pub fn try_contains_key<Q>(&self, key: &Q) -> Result<bool, LockContention>
302    where
303        Q: Hash + Equivalent<Key> + ?Sized,
304    {
305        let Some((shard, hash)) = self.shard_for(key) else {
306            return Ok(false);
307        };
308
309        match shard.try_read() {
310            Some(guard) => Ok(guard.contains(hash, key)),
311            None => Err(LockContention),
312        }
313    }
314
315    /// Fetches an item from the cache whose key is `key`.
316    pub fn get<Q>(&self, key: &Q) -> Option<Val>
317    where
318        Q: Hash + Equivalent<Key> + ?Sized,
319    {
320        let (shard, hash) = self.shard_for(key)?;
321        shard.read().get(hash, key).cloned()
322    }
323
324    /// Attempts to fetch an item from the cache whose key is `key`.
325    /// Returns `Ok(Some(val))` if the key is present, `Ok(None)` if absent,
326    /// or `Err(LockContention)` if the shard lock could not be acquired without blocking.
327    pub fn try_get<Q>(&self, key: &Q) -> Result<Option<Val>, LockContention>
328    where
329        Q: Hash + Equivalent<Key> + ?Sized,
330    {
331        let Some((shard, hash)) = self.shard_for(key) else {
332            return Ok(None);
333        };
334
335        match shard.try_read() {
336            Some(guard) => Ok(guard.get(hash, key).cloned()),
337            None => Err(LockContention),
338        }
339    }
340
341    /// Peeks an item from the cache whose key is `key`.
342    /// Contrary to gets, peeks don't alter the key "hotness".
343    pub fn peek<Q>(&self, key: &Q) -> Option<Val>
344    where
345        Q: Hash + Equivalent<Key> + ?Sized,
346    {
347        let (shard, hash) = self.shard_for(key)?;
348        shard.read().peek(hash, key).cloned()
349    }
350
351    /// Attempts to peek an item from the cache whose key is `key`.
352    /// Contrary to gets, peeks don't alter the key "hotness".
353    /// Returns `Ok(Some(val))` if the key is present, `Ok(None)` if absent,
354    /// or `Err(LockContention)` if the shard lock could not be acquired without blocking.
355    pub fn try_peek<Q>(&self, key: &Q) -> Result<Option<Val>, LockContention>
356    where
357        Q: Hash + Equivalent<Key> + ?Sized,
358    {
359        let Some((shard, hash)) = self.shard_for(key) else {
360            return Ok(None);
361        };
362        match shard.try_read() {
363            Some(guard) => Ok(guard.peek(hash, key).cloned()),
364            None => Err(LockContention),
365        }
366    }
367
368    /// Returns per-item statistics for `key`, or `None` if the key is not present.
369    /// Like peeks, this does not alter the key "hotness" or its access count.
370    #[cfg(feature = "stats")]
371    pub fn item_stats<Q>(&self, key: &Q) -> Option<crate::ItemStats>
372    where
373        Q: Hash + Equivalent<Key> + ?Sized,
374    {
375        let (shard, hash) = self.shard_for(key)?;
376        shard.read().item_stats(hash, key)
377    }
378
379    /// Attempts to return per-item statistics for `key`.
380    /// Like peeks, this does not alter the key "hotness" or its access count.
381    /// Returns `Ok(Some(stats))` if the key is present, `Ok(None)` if absent,
382    /// or `Err(LockContention)` if the shard lock could not be acquired without blocking.
383    #[cfg(feature = "stats")]
384    pub fn try_item_stats<Q>(&self, key: &Q) -> Result<Option<crate::ItemStats>, LockContention>
385    where
386        Q: Hash + Equivalent<Key> + ?Sized,
387    {
388        let Some((shard, hash)) = self.shard_for(key) else {
389            return Ok(None);
390        };
391        match shard.try_read() {
392            Some(guard) => Ok(guard.item_stats(hash, key)),
393            None => Err(LockContention),
394        }
395    }
396
397    /// Remove an item from the cache whose key is `key`.
398    /// Returns the removed entry, if any.
399    pub fn remove<Q>(&self, key: &Q) -> Option<(Key, Val)>
400    where
401        Q: Hash + Equivalent<Key> + ?Sized,
402    {
403        let (shard, hash) = self.shard_for(key).unwrap();
404        shard.write().remove(hash, key)
405    }
406
407    /// Attempts to remove an item from the cache whose key is `key`.
408    /// Returns `Ok(Some(entry))` with the removed entry if present, `Ok(None)` if absent,
409    /// or `Err(LockContention)` if the shard lock could not be acquired without blocking.
410    pub fn try_remove<Q>(&self, key: &Q) -> Result<Option<(Key, Val)>, LockContention>
411    where
412        Q: Hash + Equivalent<Key> + ?Sized,
413    {
414        let Some((shard, hash)) = self.shard_for(key) else {
415            return Ok(None);
416        };
417
418        match shard.try_write() {
419            Some(mut guard) => Ok(guard.remove(hash, key)),
420            None => Err(LockContention),
421        }
422    }
423
424    /// Remove an item from the cache whose key is `key` if `f(&value)` returns `true` for that entry.
425    /// Compared to peek and remove, this method guarantees that no new value was inserted in-between.
426    ///
427    /// Returns the removed entry, if any.
428    pub fn remove_if<Q, F>(&self, key: &Q, f: F) -> Option<(Key, Val)>
429    where
430        Q: Hash + Equivalent<Key> + ?Sized,
431        F: FnOnce(&Val) -> bool,
432    {
433        let (shard, hash) = self.shard_for(key).unwrap();
434        shard.write().remove_if(hash, key, f)
435    }
436
437    /// Inserts an item in the cache, but _only_ if an entry with key `key` already exists.
438    /// If `soft` is set, the replace operation won't affect the "hotness" of the entry,
439    /// even if the value is replaced.
440    ///
441    /// Returns `Ok` if the entry was admitted and `Err(_)` if it wasn't.
442    pub fn replace(&self, key: Key, value: Val, soft: bool) -> Result<(), (Key, Val)> {
443        let mut lcs = Default::default();
444        self.replace_with_lifecycle(key, value, soft, &mut lcs)
445    }
446
447    /// Inserts an item in the cache, but _only_ if an entry with key `key` already exists,
448    /// recording any evicted items into the given lifecycle request state.
449    /// If `soft` is set, the replace operation won't affect the "hotness" of the entry,
450    /// even if the value is replaced.
451    ///
452    /// Returns `Ok` if the entry was admitted and `Err(_)` if it wasn't.
453    ///
454    /// `lcs` is a [`Lifecycle::RequestState`]; construct one with `Default::default()`.
455    /// The same `&mut lcs` can be threaded through multiple operations to batch the
456    /// eviction work. Evicted items are released when `lcs` is dropped.
457    pub fn replace_with_lifecycle(
458        &self,
459        key: Key,
460        value: Val,
461        soft: bool,
462        lcs: &mut L::RequestState,
463    ) -> Result<(), (Key, Val)> {
464        let (shard, hash) = self.shard_for(&key).unwrap();
465        shard
466            .write()
467            .insert(lcs, hash, key, value, InsertStrategy::Replace { soft })?;
468        Ok(())
469    }
470
471    /// Retains only the items specified by the predicate.
472    /// In other words, remove all items for which `f(&key, &value)` returns `false`. The
473    /// elements are visited in arbitrary order.
474    pub fn retain<F>(&self, f: F)
475    where
476        F: Fn(&Key, &Val) -> bool,
477    {
478        for s in self.shards.iter() {
479            s.write().retain(&f);
480        }
481    }
482
483    /// Inserts an item in the cache with key `key`.
484    pub fn insert(&self, key: Key, value: Val) {
485        let mut lcs = Default::default();
486        self.insert_with_lifecycle(key, value, &mut lcs);
487    }
488
489    /// Attempts to insert an item in the cache with key `key` without blocking.
490    /// Returns `Ok(())` if the item was inserted, or `Err((key, value))` if the shard lock
491    /// could not be acquired without blocking. Lock contention is the only failure
492    /// mode: the inputs are returned so the caller can retry or discard them.
493    pub fn try_insert(&self, key: Key, value: Val) -> Result<(), (Key, Val)> {
494        let mut lcs = Default::default();
495        self.try_insert_with_lifecycle(key, value, &mut lcs)
496    }
497
498    /// Inserts an item in the cache with key `key`, recording any evicted items into the
499    /// given lifecycle request state.
500    ///
501    /// `lcs` is a [`Lifecycle::RequestState`]; construct one with `Default::default()`.
502    /// The same `&mut lcs` can be threaded through multiple operations to batch the
503    /// eviction work. Evicted items are released when `lcs` is dropped.
504    pub fn insert_with_lifecycle(&self, key: Key, value: Val, lcs: &mut L::RequestState) {
505        let (shard, hash) = self.shard_for(&key).unwrap();
506        let result = shard
507            .write()
508            .insert(lcs, hash, key, value, InsertStrategy::Insert);
509        // result cannot err with the Insert strategy
510        debug_assert!(result.is_ok());
511    }
512
513    /// Attempts to insert an item in the cache with key `key` without blocking, recording
514    /// any evicted items into the given lifecycle request state.
515    /// Returns `Ok(())` if the item was inserted, or `Err((key, value))` if the shard lock
516    /// could not be acquired without blocking. Lock contention is the only failure mode:
517    /// the inputs are returned so the caller can retry or discard them.
518    ///
519    /// `lcs` is a [`Lifecycle::RequestState`]; construct one with `Default::default()`.
520    /// The same `&mut lcs` can be threaded through multiple operations to batch the
521    /// eviction work. Evicted items are released when `lcs` is dropped.
522    pub fn try_insert_with_lifecycle(
523        &self,
524        key: Key,
525        value: Val,
526        lcs: &mut L::RequestState,
527    ) -> Result<(), (Key, Val)> {
528        let (shard, hash) = self.shard_for(&key).unwrap();
529
530        match shard.try_write() {
531            Some(mut shard) => {
532                let result = shard.insert(lcs, hash, key, value, InsertStrategy::Insert);
533                // result cannot err with the Insert strategy
534                debug_assert!(result.is_ok());
535                Ok(())
536            }
537            _ => Err((key, value)),
538        }
539    }
540
541    /// Clear all items from the cache
542    pub fn clear(&self) {
543        for s in self.shards.iter() {
544            s.write().clear();
545        }
546    }
547
548    /// Iterates over the items in the cache returning cloned key value pairs.
549    ///
550    /// The iterator is guaranteed to yield all items in the cache at the time of creation
551    /// provided that they are not removed or evicted from the cache while iterating.
552    /// The iterator may also yield items added to the cache after the iterator is created.
553    pub fn iter(&self) -> Iter<'_, Key, Val, We, B, L>
554    where
555        Key: Clone,
556    {
557        Iter {
558            shards: &self.shards,
559            current_shard: 0,
560            last: None,
561        }
562    }
563
564    /// Drains items from the cache.
565    ///
566    /// The iterator is guaranteed to drain all items in the cache at the time of creation
567    /// provided that they are not removed or evicted from the cache while draining.
568    /// The iterator may also drain items added to the cache after the iterator is created.
569    /// Due to the above, the cache may not be empty after the iterator is fully consumed
570    /// if items are added to the cache while draining.
571    ///
572    /// Note that dropping the iterator will _not_ finish the draining process, unlike other
573    /// drain methods.
574    pub fn drain(&self) -> Drain<'_, Key, Val, We, B, L> {
575        Drain {
576            shards: &self.shards,
577            current_shard: 0,
578            last: None,
579        }
580    }
581
582    /// Sets the cache to a new weight capacity.
583    ///
584    /// This will adjust the weight capacity of each shard proportionally.
585    /// If the new capacity is smaller than the current weight, items will be evicted
586    /// to bring the cache within the new limit.
587    pub fn set_capacity(&self, new_weight_capacity: u64) {
588        let shard_weight_cap = new_weight_capacity.saturating_add(self.shards.len() as u64 - 1)
589            / self.shards.len() as u64;
590        for shard in &*self.shards {
591            let mut lcs = Default::default();
592            // `lcs` drops after this statement's lock guard, releasing evicted items outside the lock.
593            shard.write().set_capacity(shard_weight_cap, &mut lcs);
594        }
595    }
596
597    /// Gets an item from the cache with key `key` .
598    ///
599    /// If the corresponding value isn't present in the cache, this function returns a guard
600    /// that can be used to insert the value once it's computed.
601    /// While the returned guard is alive, other calls with the same key using the
602    /// `get_value_or_guard` or `get_or_insert` family of functions will wait until the guard
603    /// is dropped or the value is inserted.
604    ///
605    /// A `None` `timeout` means waiting forever.
606    /// A `Some(<zero>)` timeout will return a Timeout error immediately if the value is not present
607    /// and a guard is alive elsewhere.
608    pub fn get_value_or_guard<Q>(
609        &self,
610        key: &Q,
611        timeout: Option<Duration>,
612    ) -> GuardResult<'_, Key, Val, We, B, L>
613    where
614        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
615    {
616        let (shard, hash) = self.shard_for(key).unwrap();
617        if let Some(v) = shard.read().get(hash, key) {
618            return GuardResult::Value(v.clone());
619        }
620        PlaceholderGuard::join(shard, hash, key, timeout)
621    }
622
623    /// Gets or inserts an item in the cache with key `key`.
624    ///
625    /// See also `get_value_or_guard` and `get_value_or_guard_async`.
626    pub fn get_or_insert_with<Q, E>(
627        &self,
628        key: &Q,
629        with: impl FnOnce() -> Result<Val, E>,
630    ) -> Result<Val, E>
631    where
632        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
633    {
634        match self.get_value_or_guard(key, None) {
635            GuardResult::Value(v) => Ok(v),
636            GuardResult::Guard(g) => {
637                let v = with()?;
638                let _ = g.insert(v.clone());
639                Ok(v)
640            }
641            GuardResult::Timeout => unsafe { unreachable_unchecked() },
642        }
643    }
644
645    /// Gets an item from the cache with key `key`.
646    ///
647    /// If the corresponding value isn't present in the cache, this function returns a guard
648    /// that can be used to insert the value once it's computed.
649    /// While the returned guard is alive, other calls with the same key using the
650    /// `get_value_or_guard` or `get_or_insert` family of functions will wait until the guard
651    /// is dropped or the value is inserted.
652    pub async fn get_value_or_guard_async<'a, Q>(
653        &'a self,
654        key: &Q,
655    ) -> Result<Val, PlaceholderGuard<'a, Key, Val, We, B, L>>
656    where
657        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
658    {
659        let (shard, hash) = self.shard_for(key).unwrap();
660        loop {
661            if let Some(v) = shard.read().get(hash, key) {
662                return Ok(v.clone());
663            }
664            match JoinFuture::new(shard, hash, key).await {
665                JoinResult::Filled(Some(shared)) => {
666                    // SAFETY: Filled means the value was set by the loader.
667                    return Ok(unsafe { shared.value().unwrap_unchecked().clone() });
668                }
669                JoinResult::Filled(None) => continue,
670                JoinResult::Guard(g) => return Err(g),
671                JoinResult::Timeout => unsafe { unreachable_unchecked() },
672            }
673        }
674    }
675
676    /// Gets or inserts an item in the cache with key `key`.
677    pub async fn get_or_insert_async<Q, E>(
678        &self,
679        key: &Q,
680        with: impl Future<Output = Result<Val, E>>,
681    ) -> Result<Val, E>
682    where
683        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
684    {
685        match self.get_value_or_guard_async(key).await {
686            Ok(v) => Ok(v),
687            Err(g) => {
688                let v = with.await?;
689                let _ = g.insert(v.clone());
690                Ok(v)
691            }
692        }
693    }
694
695    /// Atomically accesses an existing entry, or gets a guard for insertion.
696    ///
697    /// If a value exists for `key`, `on_occupied` is called with a mutable reference
698    /// to the key and value. The callback returns an [`EntryAction`] to decide what to do:
699    /// - [`EntryAction::Retain`]`(T)` — keep the entry, return `T`.
700    ///   Weight is recalculated after the callback returns.
701    /// - [`EntryAction::Remove`] — remove the entry from the cache.
702    /// - [`EntryAction::ReplaceWithGuard`] — remove the entry and get a guard for re-insertion.
703    ///
704    /// If no value exists, a [`PlaceholderGuard`] is returned for inserting a new value.
705    /// If another thread is already loading this key, waits up to `timeout` for the value
706    /// to arrive, then calls `on_occupied` on the result.
707    ///
708    /// A `None` `timeout` means waiting forever.
709    /// A `Some(<zero>)` timeout will return a Timeout immediately if a guard is alive elsewhere.
710    ///
711    /// The callback is `FnOnce` and runs **at most once**.
712    ///
713    /// # Performance
714    ///
715    /// Always acquires a **write lock** on the shard. For read-only lookups where
716    /// contention matters, prefer [`get`](Self::get), [`get_value_or_guard`](Self::get_value_or_guard)
717    /// or similar.
718    ///
719    /// The callback runs under the shard write lock — keep it short to avoid blocking
720    /// other operations on the same shard. **Do not** call back into the cache from the
721    /// callback, as this will deadlock when the same shard is accessed.
722    ///
723    /// # Panics
724    ///
725    /// If the callback panics, weight accounting is automatically corrected.
726    /// However, any partial mutation to the value will remain.
727    ///
728    /// # Examples
729    ///
730    /// ```
731    /// use quick_cache::sync::{Cache, EntryAction, EntryResult};
732    ///
733    /// let cache: Cache<String, u64> = Cache::new(5);
734    /// cache.insert("counter".to_string(), 0);
735    ///
736    /// // Mutate in place: increment a counter
737    /// let result = cache.entry("counter", None, |_k, v| {
738    ///     *v += 1;
739    ///     EntryAction::Retain(*v)
740    /// });
741    /// assert!(matches!(result, EntryResult::Retained(1)));
742    /// assert_eq!(cache.get("counter"), Some(1));
743    /// ```
744    pub fn entry<Q, T>(
745        &self,
746        key: &Q,
747        timeout: Option<Duration>,
748        on_occupied: impl FnOnce(&Key, &mut Val) -> EntryAction<T>,
749    ) -> EntryResult<'_, Key, Val, We, B, L, T>
750    where
751        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
752    {
753        let (shard, hash) = self.shard_for(key).unwrap();
754        // Wrap FnOnce in Option so we can pass &mut FnMut to entry_or_placeholder
755        // in a loop. The loop retries only on ExistingPlaceholder (another thread is
756        // loading), which does not invoke the callback — so the Option is still Some
757        // on retry and the callback runs at most once.
758        let mut on_occupied = Some(on_occupied);
759        let mut callback = |k: &Key, v: &mut Val| on_occupied.take().unwrap()(k, v);
760        let mut deadline = timeout.map(Ok);
761
762        loop {
763            let mut shard_guard = shard.write();
764            match shard_guard.entry_or_placeholder(hash, key, &mut callback) {
765                EntryOrPlaceholder::Kept(t) => return EntryResult::Retained(t),
766                EntryOrPlaceholder::Removed(k, v) => return EntryResult::Removed(k, v),
767                EntryOrPlaceholder::Replaced(shared, old_val) => {
768                    drop(shard_guard);
769                    return EntryResult::Replaced(
770                        PlaceholderGuard::start_loading(shard, shared),
771                        old_val,
772                    );
773                }
774                EntryOrPlaceholder::NewPlaceholder(shared) => {
775                    drop(shard_guard);
776                    return EntryResult::Vacant(PlaceholderGuard::start_loading(shard, shared));
777                }
778                EntryOrPlaceholder::ExistingPlaceholder(shared) => {
779                    match PlaceholderGuard::wait_for_placeholder(
780                        shard,
781                        shard_guard,
782                        shared,
783                        deadline.as_mut(),
784                    ) {
785                        JoinResult::Filled(_) => continue,
786                        JoinResult::Guard(g) => return EntryResult::Vacant(g),
787                        JoinResult::Timeout => return EntryResult::Timeout,
788                    }
789                }
790            }
791        }
792    }
793
794    /// Async version of [`Self::entry`].
795    ///
796    /// Atomically accesses an existing entry, or gets a guard for insertion.
797    /// If another task is already loading this key, waits asynchronously for the value.
798    ///
799    /// See [`entry`](Self::entry) for full documentation.
800    pub async fn entry_async<'a, Q, T>(
801        &'a self,
802        key: &Q,
803        on_occupied: impl FnOnce(&Key, &mut Val) -> EntryAction<T>,
804    ) -> EntryResult<'a, Key, Val, We, B, L, T>
805    where
806        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
807    {
808        let (shard, hash) = self.shard_for(key).unwrap();
809        // See entry() for explanation of the Option::take pattern.
810        let mut on_occupied = Some(on_occupied);
811        let mut callback = |k: &Key, v: &mut Val| on_occupied.take().unwrap()(k, v);
812
813        loop {
814            // Scope the write guard so it doesn't appear in the async state machine,
815            // which would make the future !Send.
816            let result = {
817                let mut shard_guard = shard.write();
818                match shard_guard.entry_or_placeholder(hash, key, &mut callback) {
819                    EntryOrPlaceholder::Kept(t) => Ok(EntryResult::Retained(t)),
820                    EntryOrPlaceholder::Removed(k, v) => Ok(EntryResult::Removed(k, v)),
821                    EntryOrPlaceholder::Replaced(shared, old_val) => {
822                        drop(shard_guard);
823                        Ok(EntryResult::Replaced(
824                            PlaceholderGuard::start_loading(shard, shared),
825                            old_val,
826                        ))
827                    }
828                    EntryOrPlaceholder::NewPlaceholder(shared) => {
829                        drop(shard_guard);
830                        Ok(EntryResult::Vacant(PlaceholderGuard::start_loading(
831                            shard, shared,
832                        )))
833                    }
834                    EntryOrPlaceholder::ExistingPlaceholder(_) => Err(()),
835                }
836            };
837            match result {
838                Ok(entry_result) => return entry_result,
839                Err(()) => match JoinFuture::new(shard, hash, key).await {
840                    JoinResult::Filled(_) => continue,
841                    JoinResult::Guard(g) => return EntryResult::Vacant(g),
842                    JoinResult::Timeout => unsafe { unreachable_unchecked() },
843                },
844            }
845        }
846    }
847
848    /// Get total memory used by cache data structures
849    ///
850    /// It should be noted that if cache key or value is some type like `Vec<T>`,
851    /// the memory allocated in the heap will not be counted.
852    pub fn memory_used(&self) -> MemoryUsed {
853        let mut total = MemoryUsed { entries: 0, map: 0 };
854        self.shards.iter().for_each(|shard| {
855            let shard_memory = shard.read().memory_used();
856            total.entries += shard_memory.entries;
857            total.map += shard_memory.map;
858        });
859        total
860    }
861}
862
863impl<Key, Val, We, B, L> std::fmt::Debug for Cache<Key, Val, We, B, L> {
864    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
865        f.debug_struct("Cache").finish_non_exhaustive()
866    }
867}
868
869/// Iterator over the items in the cache.
870///
871/// See [`Cache::iter`] for more details.
872pub struct Iter<'a, Key, Val, We, B, L> {
873    shards: &'a [RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>],
874    current_shard: usize,
875    last: Option<Token>,
876}
877
878impl<Key, Val, We, B, L> Iterator for Iter<'_, Key, Val, We, B, L>
879where
880    Key: Clone,
881    Val: Clone,
882{
883    type Item = (Key, Val);
884
885    fn next(&mut self) -> Option<Self::Item> {
886        while self.current_shard < self.shards.len() {
887            let shard = &self.shards[self.current_shard];
888            let lock = shard.read();
889            if let Some((new_last, key, val)) = lock.iter_from(self.last).next() {
890                self.last = Some(new_last);
891                return Some((key.clone(), val.clone()));
892            }
893            self.last = None;
894            self.current_shard += 1;
895        }
896        None
897    }
898}
899
900impl<Key, Val, We, B, L> std::fmt::Debug for Iter<'_, Key, Val, We, B, L> {
901    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
902        f.debug_struct("Iter").finish_non_exhaustive()
903    }
904}
905
906/// Draining iterator for the items in the cache.
907///
908/// See [`Cache::drain`] for more details.
909pub struct Drain<'a, Key, Val, We, B, L> {
910    shards: &'a [RwLock<CacheShard<Key, Val, We, B, L, SharedPlaceholder<Val>>>],
911    current_shard: usize,
912    last: Option<Token>,
913}
914
915impl<Key, Val, We, B, L> Iterator for Drain<'_, Key, Val, We, B, L>
916where
917    Key: Hash + Eq,
918    We: Weighter<Key, Val>,
919    B: BuildHasher,
920    L: Lifecycle<Key, Val>,
921{
922    type Item = (Key, Val);
923
924    fn next(&mut self) -> Option<Self::Item> {
925        while self.current_shard < self.shards.len() {
926            let shard = &self.shards[self.current_shard];
927            let mut lock = shard.write();
928            if let Some((new_last, key, value)) = lock.remove_next(self.last) {
929                self.last = Some(new_last);
930                return Some((key, value));
931            }
932            self.last = None;
933            self.current_shard += 1;
934        }
935        None
936    }
937}
938
939impl<Key, Val, We, B, L> std::fmt::Debug for Drain<'_, Key, Val, We, B, L> {
940    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
941        f.debug_struct("Drain").finish_non_exhaustive()
942    }
943}
944
945/// Default `Lifecycle` for a sync cache.
946///
947/// Stashes up to two evicted items for dropping them outside the cache locks.
948pub struct DefaultLifecycle<Key, Val>(std::marker::PhantomData<(Key, Val)>);
949
950impl<Key, Val> std::fmt::Debug for DefaultLifecycle<Key, Val> {
951    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
952        f.debug_tuple("DefaultLifecycle").finish()
953    }
954}
955
956impl<Key, Val> Default for DefaultLifecycle<Key, Val> {
957    #[inline]
958    fn default() -> Self {
959        Self(Default::default())
960    }
961}
962impl<Key, Val> Clone for DefaultLifecycle<Key, Val> {
963    #[inline]
964    fn clone(&self) -> Self {
965        Self(Default::default())
966    }
967}
968
969impl<Key, Val> Lifecycle<Key, Val> for DefaultLifecycle<Key, Val> {
970    // Why two items?
971    // Because assuming the cache has roughly similarly weighted items,
972    // we can expect that at one or two items will be evicted per request
973    // in most cases. And we want to avoid introducing any extra
974    // overhead (e.g. a vector) for this default lifecycle.
975    type RequestState = [Option<(Key, Val)>; 2];
976
977    #[inline]
978    fn on_evict(&self, state: &mut Self::RequestState, key: Key, val: Val) {
979        if std::mem::needs_drop::<(Key, Val)>() {
980            if state[0].is_none() {
981                state[0] = Some((key, val));
982            } else if state[1].is_none() {
983                state[1] = Some((key, val));
984            }
985        }
986    }
987}
988
989#[cfg(test)]
990mod tests {
991    use super::*;
992    use crate::shard::SharedPlaceholder as _;
993    use std::{
994        sync::{Arc, Barrier},
995        thread,
996    };
997
998    #[test]
999    #[cfg_attr(miri, ignore)]
1000    fn test_multiple_threads() {
1001        const N_THREAD_PAIRS: usize = 8;
1002        const N_ROUNDS: usize = 1_000;
1003        const ITEMS_PER_THREAD: usize = 1_000;
1004        let mut threads = Vec::new();
1005        let barrier = Arc::new(Barrier::new(N_THREAD_PAIRS * 2));
1006        let cache = Arc::new(Cache::new(N_THREAD_PAIRS * ITEMS_PER_THREAD / 10));
1007        for t in 0..N_THREAD_PAIRS {
1008            let barrier = barrier.clone();
1009            let cache = cache.clone();
1010            let handle = thread::spawn(move || {
1011                let start = ITEMS_PER_THREAD * t;
1012                barrier.wait();
1013                for _round in 0..N_ROUNDS {
1014                    for i in start..start + ITEMS_PER_THREAD {
1015                        cache.insert(i, i);
1016                    }
1017                }
1018            });
1019            threads.push(handle);
1020        }
1021        for t in 0..N_THREAD_PAIRS {
1022            let barrier = barrier.clone();
1023            let cache = cache.clone();
1024            let handle = thread::spawn(move || {
1025                let start = ITEMS_PER_THREAD * t;
1026                barrier.wait();
1027                for _round in 0..N_ROUNDS {
1028                    for i in start..start + ITEMS_PER_THREAD {
1029                        if let Some(cached) = cache.get(&i) {
1030                            assert_eq!(cached, i);
1031                        }
1032                    }
1033                }
1034            });
1035            threads.push(handle);
1036        }
1037        for t in threads {
1038            t.join().unwrap();
1039        }
1040    }
1041
1042    #[test]
1043    fn test_iter() {
1044        let capacity = if cfg!(miri) { 100 } else { 100000 };
1045        let options = OptionsBuilder::new()
1046            .estimated_items_capacity(capacity)
1047            .weight_capacity(capacity as u64)
1048            .shards(2)
1049            .build()
1050            .unwrap();
1051        let cache = Cache::with_options(
1052            options,
1053            UnitWeighter,
1054            DefaultHashBuilder::default(),
1055            DefaultLifecycle::default(),
1056        );
1057        let items = capacity / 2;
1058        for i in 0..items {
1059            cache.insert(i, i);
1060        }
1061        assert_eq!(cache.len(), items);
1062        let mut iter_collected = cache.iter().collect::<Vec<_>>();
1063        assert_eq!(iter_collected.len(), items);
1064        iter_collected.sort();
1065        for (i, v) in iter_collected.into_iter().enumerate() {
1066            assert_eq!((i, i), v);
1067        }
1068    }
1069
1070    #[test]
1071    fn test_drain() {
1072        let capacity = if cfg!(miri) { 100 } else { 100000 };
1073        let options = OptionsBuilder::new()
1074            .estimated_items_capacity(capacity)
1075            .weight_capacity(capacity as u64)
1076            .shards(2)
1077            .build()
1078            .unwrap();
1079        let cache = Cache::with_options(
1080            options,
1081            UnitWeighter,
1082            DefaultHashBuilder::default(),
1083            DefaultLifecycle::default(),
1084        );
1085        let items = capacity / 2;
1086        for i in 0..items {
1087            cache.insert(i, i);
1088        }
1089        assert_eq!(cache.len(), items);
1090        let mut drain_collected = cache.drain().collect::<Vec<_>>();
1091        assert_eq!(cache.len(), 0);
1092        assert_eq!(drain_collected.len(), items);
1093        drain_collected.sort();
1094        for (i, v) in drain_collected.into_iter().enumerate() {
1095            assert_eq!((i, i), v);
1096        }
1097    }
1098
1099    #[test]
1100    fn test_set_capacity() {
1101        let cache = Cache::new(100);
1102        for i in 0..80 {
1103            cache.insert(i, i);
1104        }
1105        let initial_len = cache.len();
1106        assert!(initial_len <= 80);
1107
1108        // Set to smaller capacity
1109        cache.set_capacity(50);
1110        assert!(cache.len() <= 50);
1111        assert!(cache.weight() <= 50);
1112
1113        // Set to larger capacity
1114        cache.set_capacity(200);
1115        assert_eq!(cache.capacity(), 200);
1116
1117        // Insert more items
1118        for i in 100..180 {
1119            cache.insert(i, i);
1120        }
1121        assert!(cache.len() <= 180);
1122        assert!(cache.weight() <= 200);
1123    }
1124
1125    #[test]
1126    fn test_remove_if() {
1127        let cache = Cache::new(100);
1128
1129        // Insert test data
1130        cache.insert(1, 10);
1131        cache.insert(2, 20);
1132        cache.insert(3, 30);
1133
1134        // Test removing with predicate that returns true
1135        let removed = cache.remove_if(&2, |v| *v == 20);
1136        assert_eq!(removed, Some((2, 20)));
1137        assert_eq!(cache.get(&2), None);
1138
1139        // Test removing with predicate that returns false
1140        let not_removed = cache.remove_if(&3, |v| *v == 999);
1141        assert_eq!(not_removed, None);
1142        assert_eq!(cache.get(&3), Some(30));
1143
1144        // Test removing non-existent key
1145        let not_found = cache.remove_if(&999, |_| true);
1146        assert_eq!(not_found, None);
1147    }
1148
1149    /// Tests all basic entry actions: Retain, Remove, ReplaceWithGuard, Vacant, mutate+Retain
1150    #[test]
1151    fn test_entry_actions() {
1152        let cache = Cache::new(100);
1153        cache.insert(1, 10);
1154        cache.insert(2, 20);
1155
1156        // Retain returns the value via callback, entry stays
1157        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1158        assert!(matches!(result, EntryResult::Retained(10)));
1159        assert_eq!(cache.get(&1), Some(10));
1160
1161        // Mutate in place via Retain
1162        let result = cache.entry(&1, None, |_k, v| {
1163            *v += 5;
1164            EntryAction::Retain(())
1165        });
1166        assert!(matches!(result, EntryResult::Retained(())));
1167        assert_eq!(cache.get(&1), Some(15));
1168
1169        // Remove
1170        let result = cache.entry(&1, None, |_k, _v| EntryAction::<()>::Remove);
1171        assert!(matches!(result, EntryResult::Removed(1, 15)));
1172        assert_eq!(cache.get(&1), None);
1173
1174        // Remove then re-enter same key → Vacant
1175        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1176        match result {
1177            EntryResult::Vacant(g) => {
1178                let _ = g.insert(99);
1179                assert_eq!(cache.get(&1), Some(99));
1180            }
1181            _ => panic!("expected Vacant for removed key"),
1182        }
1183
1184        // ReplaceWithGuard: capture old value, get guard, insert new
1185        let mut old_val = 0;
1186        let result = cache.entry(&2, None, |_k, v| {
1187            old_val = *v;
1188            EntryAction::<()>::ReplaceWithGuard
1189        });
1190        assert_eq!(old_val, 20);
1191        match result {
1192            EntryResult::Replaced(g, old) => {
1193                assert_eq!(old, 20);
1194                let _ = g.insert(old_val + 100);
1195                assert_eq!(cache.get(&2), Some(120));
1196            }
1197            _ => panic!("expected Replaced"),
1198        }
1199
1200        // ReplaceWithGuard then abandon guard → entry gone
1201        let result = cache.entry(&2, None, |_k, _v| EntryAction::<()>::ReplaceWithGuard);
1202        match result {
1203            EntryResult::Replaced(g, _old) => {
1204                drop(g);
1205                assert_eq!(cache.get(&2), None);
1206            }
1207            _ => panic!("expected Replaced"),
1208        }
1209
1210        // Vacant key → guard
1211        let result = cache.entry(&3, None, |_k, v| EntryAction::Retain(*v));
1212        match result {
1213            EntryResult::Vacant(g) => {
1214                let _ = g.insert(30);
1215                assert_eq!(cache.get(&3), Some(30));
1216            }
1217            _ => panic!("expected Vacant"),
1218        }
1219    }
1220
1221    /// Tests weight tracking across all entry actions using a string-length weighter
1222    #[test]
1223    fn test_entry_weight_tracking() {
1224        #[derive(Clone)]
1225        struct StringWeighter;
1226        impl crate::Weighter<u64, String> for StringWeighter {
1227            fn weight(&self, _key: &u64, val: &String) -> u64 {
1228                val.len() as u64
1229            }
1230        }
1231
1232        let cache = Cache::with_weighter(100, 100_000, StringWeighter);
1233        cache.insert(1, "hello".to_string());
1234        cache.insert(2, "world".to_string());
1235        assert_eq!(cache.weight(), 10);
1236
1237        // Retain without mutation — weight unchanged
1238        let result = cache.entry(&1, None, |_k, _v| EntryAction::Retain(()));
1239        assert!(matches!(result, EntryResult::Retained(())));
1240        assert_eq!(cache.weight(), 10);
1241
1242        // Mutate to longer string — weight increases
1243        let result = cache.entry(&1, None, |_k, v| {
1244            v.push_str(" world");
1245            EntryAction::Retain(())
1246        });
1247        assert!(matches!(result, EntryResult::Retained(())));
1248        assert_eq!(cache.weight(), 16); // "hello world" (11) + "world" (5)
1249        assert_eq!(cache.get(&1).unwrap(), "hello world");
1250
1251        // Mutate to empty string — weight to zero, entry stays
1252        let result = cache.entry(&1, None, |_k, v| {
1253            v.clear();
1254            EntryAction::Retain(())
1255        });
1256        assert!(matches!(result, EntryResult::Retained(())));
1257        assert_eq!(cache.weight(), 5); // "" (0) + "world" (5)
1258        assert_eq!(cache.get(&1).unwrap(), "");
1259
1260        // Remove — weight decremented
1261        let result = cache.entry(&2, None, |_k, _v| EntryAction::<()>::Remove);
1262        assert!(matches!(result, EntryResult::Removed(2, _)));
1263        assert_eq!(cache.weight(), 0);
1264        assert_eq!(cache.len(), 1);
1265
1266        // ReplaceWithGuard — old weight gone, new weight after insert
1267        cache.insert(3, "hello".to_string());
1268        assert_eq!(cache.weight(), 5);
1269        let result = cache.entry(&3, None, |_k, _v| EntryAction::<()>::ReplaceWithGuard);
1270        match result {
1271            EntryResult::Replaced(g, _old) => {
1272                assert_eq!(cache.weight(), 0);
1273                let _ = g.insert("hello world!!".to_string());
1274                assert_eq!(cache.weight(), 13);
1275            }
1276            _ => panic!("expected Replaced"),
1277        }
1278    }
1279
1280    /// Tests eviction and zero-capacity edge cases
1281    #[test]
1282    fn test_entry_eviction() {
1283        // Cache with capacity for ~2 items — insert 3rd triggers eviction
1284        let cache = Cache::new(2);
1285        cache.insert(1, 10);
1286        cache.insert(2, 20);
1287        assert_eq!(cache.len(), 2);
1288
1289        let result = cache.entry(&3, None, |_k, v| EntryAction::Retain(*v));
1290        match result {
1291            EntryResult::Vacant(g) => {
1292                let _ = g.insert(30);
1293                assert!(cache.len() <= 2);
1294                assert_eq!(cache.get(&3), Some(30));
1295            }
1296            _ => panic!("expected Vacant"),
1297        }
1298
1299        // Zero-capacity cache — insert evicts immediately
1300        let cache = Cache::new(0);
1301        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1302        match result {
1303            EntryResult::Vacant(g) => {
1304                let _ = g.insert(10);
1305                assert_eq!(cache.get(&1), None);
1306            }
1307            _ => panic!("expected Vacant"),
1308        }
1309    }
1310
1311    /// Tests entry() waiting on existing placeholder: value arrives, guard abandoned
1312    #[test]
1313    #[cfg_attr(miri, ignore)]
1314    fn test_entry_concurrent_placeholder_wait() {
1315        let cache = Arc::new(Cache::new(100));
1316        let barrier = Arc::new(Barrier::new(2));
1317
1318        // Thread holds guard, inserts after delay
1319        let cache2 = cache.clone();
1320        let barrier2 = barrier.clone();
1321        let handle = thread::spawn(move || match cache2.get_value_or_guard(&1, None) {
1322            GuardResult::Guard(g) => {
1323                barrier2.wait();
1324                std::thread::sleep(Duration::from_millis(50));
1325                let _ = g.insert(42);
1326            }
1327            _ => panic!("expected guard"),
1328        });
1329
1330        barrier.wait();
1331        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1332        assert!(matches!(result, EntryResult::Retained(42)));
1333        handle.join().unwrap();
1334    }
1335
1336    /// Tests entry() getting guard when placeholder loader abandons
1337    #[test]
1338    #[cfg_attr(miri, ignore)]
1339    fn test_entry_concurrent_placeholder_guard_abandoned() {
1340        let cache = Arc::new(Cache::new(100));
1341        let barrier = Arc::new(Barrier::new(2));
1342
1343        let cache2 = cache.clone();
1344        let barrier2 = barrier.clone();
1345        let handle = thread::spawn(move || match cache2.get_value_or_guard(&1, None) {
1346            GuardResult::Guard(g) => {
1347                barrier2.wait();
1348                std::thread::sleep(Duration::from_millis(50));
1349                drop(g);
1350            }
1351            _ => panic!("expected guard"),
1352        });
1353
1354        barrier.wait();
1355        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1356        match result {
1357            EntryResult::Vacant(g) => {
1358                let _ = g.insert(99);
1359                assert_eq!(cache.get(&1), Some(99));
1360            }
1361            _ => panic!("expected Vacant after abandoned placeholder"),
1362        }
1363        handle.join().unwrap();
1364    }
1365
1366    /// Tests zero and nonzero timeouts
1367    #[test]
1368    #[cfg_attr(miri, ignore)]
1369    fn test_entry_timeout() {
1370        let cache = Cache::new(100);
1371
1372        // Zero timeout — immediate Timeout when placeholder exists
1373        let guard = match cache.get_value_or_guard(&1, None) {
1374            GuardResult::Guard(g) => g,
1375            _ => panic!("expected guard"),
1376        };
1377        let result = cache.entry(&1, Some(Duration::ZERO), |_k, v| EntryAction::Retain(*v));
1378        assert!(matches!(result, EntryResult::Timeout));
1379        let _ = guard.insert(1);
1380
1381        // Nonzero timeout — guard held longer than timeout
1382        let cache = Arc::new(Cache::new(100));
1383        let barrier = Arc::new(Barrier::new(2));
1384        let cache2 = cache.clone();
1385        let barrier2 = barrier.clone();
1386        let holder = thread::spawn(move || {
1387            let guard = match cache2.get_value_or_guard(&1, None) {
1388                GuardResult::Guard(g) => g,
1389                _ => panic!("expected guard"),
1390            };
1391            barrier2.wait();
1392            std::thread::sleep(Duration::from_millis(200));
1393            let _ = guard.insert(1);
1394        });
1395
1396        barrier.wait();
1397        let result = cache.entry(&1, Some(Duration::from_millis(50)), |_k, v| {
1398            EntryAction::Retain(*v)
1399        });
1400        assert!(matches!(result, EntryResult::Timeout));
1401        holder.join().unwrap();
1402    }
1403
1404    /// Tests multiple waiters all receiving the value
1405    #[test]
1406    #[cfg_attr(miri, ignore)]
1407    fn test_entry_concurrent_multiple_waiters() {
1408        let cache = Arc::new(Cache::new(100));
1409        let barrier = Arc::new(Barrier::new(4)); // 1 loader + 3 waiters
1410
1411        let cache1 = cache.clone();
1412        let barrier1 = barrier.clone();
1413        let loader = thread::spawn(move || match cache1.get_value_or_guard(&1, None) {
1414            GuardResult::Guard(g) => {
1415                barrier1.wait();
1416                std::thread::sleep(Duration::from_millis(50));
1417                let _ = g.insert(42);
1418            }
1419            _ => panic!("expected guard"),
1420        });
1421
1422        let mut waiters = Vec::new();
1423        for _ in 0..3 {
1424            let cache_c = cache.clone();
1425            let barrier_c = barrier.clone();
1426            waiters.push(thread::spawn(move || {
1427                barrier_c.wait();
1428                let result = cache_c.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1429                match result {
1430                    EntryResult::Retained(v) => v,
1431                    _ => panic!("expected Value"),
1432                }
1433            }));
1434        }
1435
1436        loader.join().unwrap();
1437        for w in waiters {
1438            assert_eq!(w.join().unwrap(), 42);
1439        }
1440    }
1441
1442    /// Tests ReplaceWithGuard and Remove actions after waiting for a placeholder
1443    #[test]
1444    #[cfg_attr(miri, ignore)]
1445    fn test_entry_concurrent_action_after_wait() {
1446        // ReplaceWithGuard after wait
1447        let cache = Arc::new(Cache::new(100));
1448        let barrier = Arc::new(Barrier::new(2));
1449
1450        let cache1 = cache.clone();
1451        let barrier1 = barrier.clone();
1452        let loader = thread::spawn(move || match cache1.get_value_or_guard(&1, None) {
1453            GuardResult::Guard(g) => {
1454                barrier1.wait();
1455                std::thread::sleep(Duration::from_millis(50));
1456                let _ = g.insert(42);
1457            }
1458            _ => panic!("expected guard"),
1459        });
1460
1461        barrier.wait();
1462        let result = cache.entry(&1, None, |_k, _v| EntryAction::<()>::ReplaceWithGuard);
1463        match result {
1464            EntryResult::Replaced(g, old) => {
1465                assert_eq!(old, 42);
1466                let _ = g.insert(100);
1467                assert_eq!(cache.get(&1), Some(100));
1468            }
1469            _ => panic!("expected Replaced"),
1470        }
1471        loader.join().unwrap();
1472
1473        // Remove after wait
1474        let cache = Arc::new(Cache::new(100));
1475        let barrier = Arc::new(Barrier::new(2));
1476
1477        let cache1 = cache.clone();
1478        let barrier1 = barrier.clone();
1479        let loader = thread::spawn(move || match cache1.get_value_or_guard(&1, None) {
1480            GuardResult::Guard(g) => {
1481                barrier1.wait();
1482                std::thread::sleep(Duration::from_millis(50));
1483                let _ = g.insert(42);
1484            }
1485            _ => panic!("expected guard"),
1486        });
1487
1488        barrier.wait();
1489        let result = cache.entry(&1, None, |_k, _v| EntryAction::<()>::Remove);
1490        assert!(matches!(result, EntryResult::Removed(1, 42)));
1491        assert_eq!(cache.get(&1), None);
1492        loader.join().unwrap();
1493    }
1494
1495    /// Multi-thread stress test for entry()
1496    #[test]
1497    #[cfg_attr(miri, ignore)]
1498    fn test_entry_concurrent_stress() {
1499        const N_THREADS: usize = 8;
1500        const N_KEYS: usize = 50;
1501        const N_OPS: usize = 500;
1502
1503        let cache = Arc::new(Cache::new(1000));
1504        let barrier = Arc::new(Barrier::new(N_THREADS));
1505
1506        let mut handles = Vec::new();
1507        for t in 0..N_THREADS {
1508            let cache = cache.clone();
1509            let barrier = barrier.clone();
1510            handles.push(thread::spawn(move || {
1511                barrier.wait();
1512                for i in 0..N_OPS {
1513                    let key = (t * N_OPS + i) % N_KEYS;
1514                    let result = cache.entry(&key, Some(Duration::from_millis(10)), |_k, v| {
1515                        EntryAction::Retain(*v)
1516                    });
1517                    match result {
1518                        EntryResult::Retained(_) => {}
1519                        EntryResult::Vacant(g) => {
1520                            let _ = g.insert(key * 10);
1521                        }
1522                        EntryResult::Replaced(g, _) => {
1523                            let _ = g.insert(key * 10);
1524                        }
1525                        EntryResult::Timeout => {}
1526                        EntryResult::Removed(_, _) => {}
1527                    }
1528                }
1529            }));
1530        }
1531
1532        for h in handles {
1533            h.join().unwrap();
1534        }
1535
1536        assert!(cache.len() <= N_KEYS);
1537        for key in 0..N_KEYS {
1538            if let Some(v) = cache.get(&key) {
1539                assert_eq!(v, key * 10);
1540            }
1541        }
1542    }
1543
1544    // --- Async tests ---
1545
1546    /// Tests all basic async entry actions in one test
1547    #[tokio::test]
1548    async fn test_entry_async_actions() {
1549        let cache = Cache::new(100);
1550        cache.insert(1, 10);
1551        cache.insert(2, 20);
1552
1553        // Retain
1554        let result = cache.entry_async(&1, |_k, v| EntryAction::Retain(*v)).await;
1555        assert!(matches!(result, EntryResult::Retained(10)));
1556        assert_eq!(cache.get(&1), Some(10));
1557
1558        // Remove
1559        let result = cache
1560            .entry_async(&1, |_k, _v| EntryAction::<()>::Remove)
1561            .await;
1562        assert!(matches!(result, EntryResult::Removed(1, 10)));
1563        assert_eq!(cache.get(&1), None);
1564
1565        // ReplaceWithGuard
1566        let result = cache
1567            .entry_async(&2, |_k, _v| EntryAction::<()>::ReplaceWithGuard)
1568            .await;
1569        match result {
1570            EntryResult::Replaced(g, old) => {
1571                assert_eq!(old, 20);
1572                let _ = g.insert(42);
1573                assert_eq!(cache.get(&2), Some(42));
1574            }
1575            _ => panic!("expected Replaced"),
1576        }
1577
1578        // Vacant
1579        let result = cache.entry_async(&3, |_k, v| EntryAction::Retain(*v)).await;
1580        match result {
1581            EntryResult::Vacant(g) => {
1582                let _ = g.insert(99);
1583                assert_eq!(cache.get(&3), Some(99));
1584            }
1585            _ => panic!("expected Vacant"),
1586        }
1587    }
1588
1589    /// Tests async entry waiting on placeholder: value arrives, guard abandoned
1590    #[tokio::test(flavor = "multi_thread")]
1591    async fn test_entry_async_concurrent_wait() {
1592        let cache = Arc::new(Cache::new(100));
1593        let barrier = Arc::new(Barrier::new(2));
1594
1595        let cache1 = cache.clone();
1596        let barrier1 = barrier.clone();
1597        let holder = thread::spawn(move || {
1598            let guard = match cache1.get_value_or_guard(&1, None) {
1599                GuardResult::Guard(g) => g,
1600                _ => panic!("expected guard"),
1601            };
1602            barrier1.wait();
1603            std::thread::sleep(Duration::from_millis(50));
1604            let _ = guard.insert(42);
1605        });
1606
1607        barrier.wait();
1608        let result = cache.entry_async(&1, |_k, v| EntryAction::Retain(*v)).await;
1609        assert!(matches!(result, EntryResult::Retained(42)));
1610        holder.join().unwrap();
1611    }
1612
1613    /// Tests async entry getting guard when placeholder loader abandons
1614    #[tokio::test(flavor = "multi_thread")]
1615    async fn test_entry_async_concurrent_guard_abandoned() {
1616        let cache = Arc::new(Cache::new(100));
1617        let barrier = Arc::new(Barrier::new(2));
1618
1619        let cache1 = cache.clone();
1620        let barrier1 = barrier.clone();
1621        let holder = thread::spawn(move || {
1622            let guard = match cache1.get_value_or_guard(&1, None) {
1623                GuardResult::Guard(g) => g,
1624                _ => panic!("expected guard"),
1625            };
1626            barrier1.wait();
1627            std::thread::sleep(Duration::from_millis(50));
1628            drop(guard);
1629        });
1630
1631        barrier.wait();
1632        let result = cache.entry_async(&1, |_k, v| EntryAction::Retain(*v)).await;
1633        match result {
1634            EntryResult::Vacant(g) => {
1635                let _ = g.insert(99);
1636            }
1637            _ => panic!("expected Vacant after abandoned placeholder"),
1638        }
1639        assert_eq!(cache.get(&1), Some(99));
1640        holder.join().unwrap();
1641    }
1642
1643    /// Multi-task async stress test
1644    #[tokio::test(flavor = "multi_thread")]
1645    #[cfg_attr(miri, ignore)]
1646    async fn test_entry_async_concurrent_stress() {
1647        const N_TASKS: usize = 16;
1648        const N_KEYS: usize = 50;
1649        const N_OPS: usize = 200;
1650
1651        let cache = Arc::new(Cache::new(1000));
1652        let barrier = Arc::new(tokio::sync::Barrier::new(N_TASKS));
1653
1654        let mut handles = Vec::new();
1655        for t in 0..N_TASKS {
1656            let cache = cache.clone();
1657            let barrier = barrier.clone();
1658            handles.push(tokio::spawn(async move {
1659                barrier.wait().await;
1660                for i in 0..N_OPS {
1661                    let key = (t * N_OPS + i) % N_KEYS;
1662                    // Use get_or_insert_async instead of entry_async to avoid
1663                    // lifetime issues with tokio::spawn (entry_async borrows &self)
1664                    let _ = cache
1665                        .get_or_insert_async(&key, async { Ok::<_, ()>(key * 10) })
1666                        .await;
1667                }
1668            }));
1669        }
1670
1671        for h in handles {
1672            h.await.unwrap();
1673        }
1674
1675        assert!(cache.len() <= N_KEYS);
1676        for key in 0..N_KEYS {
1677            if let Some(v) = cache.get(&key) {
1678                assert_eq!(v, key * 10);
1679            }
1680        }
1681    }
1682
1683    // --- Non-blocking method tests ---
1684    #[test]
1685    fn test_try_contains_key() {
1686        let cache = Cache::new(100);
1687        cache.insert(1, 10);
1688
1689        assert!(cache.try_contains_key(&1).is_ok_and(|v| v));
1690        assert!(cache.try_contains_key(&2).is_ok_and(|v| !v));
1691    }
1692
1693    #[test]
1694    fn test_try_contains_key_contended() {
1695        let cache = Cache::new(100);
1696        cache.insert(1, 10);
1697        // Hold write locks on all shards so try_read is blocked.
1698        let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect();
1699        assert!(cache.try_contains_key(&1).is_err());
1700    }
1701
1702    #[test]
1703    fn test_try_get() {
1704        let cache = Cache::new(100);
1705        cache.insert(1, 10);
1706
1707        assert!(cache.try_get(&1).is_ok_and(|v| matches!(v, Some(10))));
1708        assert!(cache.try_get(&2).is_ok_and(|v| v.is_none()));
1709    }
1710
1711    #[test]
1712    fn test_try_get_contended() {
1713        let cache = Cache::new(100);
1714        cache.insert(1, 10);
1715        let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect();
1716        assert!(cache.try_get(&1).is_err());
1717    }
1718
1719    #[test]
1720    fn test_try_peek() {
1721        let cache = Cache::new(100);
1722        cache.insert(1, 10);
1723
1724        assert!(cache.try_peek(&1).is_ok_and(|v| matches!(v, Some(10))));
1725        assert!(cache.try_peek(&2).is_ok_and(|v| v.is_none()));
1726    }
1727
1728    #[test]
1729    fn test_try_peek_contended() {
1730        let cache = Cache::new(100);
1731        cache.insert(1, 10);
1732        let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect();
1733        assert!(cache.try_peek(&1).is_err());
1734    }
1735
1736    #[cfg(feature = "stats")]
1737    #[test]
1738    fn test_item_stats() {
1739        let cache = Cache::new(100);
1740        // Missing key has no stats.
1741        assert!(cache.item_stats(&1).is_none());
1742
1743        cache.insert(1, 10);
1744        // Insert alone is not a hit.
1745        assert_eq!(cache.item_stats(&1).map(|s| s.access_count), Some(0));
1746
1747        // Each get increments the per-item access count.
1748        cache.get(&1);
1749        cache.get(&1);
1750        cache.get(&1);
1751        assert_eq!(cache.item_stats(&1).map(|s| s.access_count), Some(3));
1752
1753        // Peeking (including item_stats itself) does not alter the count.
1754        cache.peek(&1);
1755        let _ = cache.item_stats(&1);
1756        assert_eq!(cache.item_stats(&1).map(|s| s.access_count), Some(3));
1757    }
1758
1759    #[test]
1760    fn test_try_remove() {
1761        let cache = Cache::new(100);
1762        cache.insert(1, 10);
1763
1764        assert!(cache
1765            .try_remove(&1)
1766            .is_ok_and(|v| matches!(v, Some((1, 10)))));
1767        assert!(cache.try_remove(&1).is_ok_and(|v| v.is_none()));
1768        assert!(cache.try_remove(&99).is_ok_and(|v| v.is_none()));
1769    }
1770
1771    #[test]
1772    fn test_try_remove_contended() {
1773        let cache = Cache::new(100);
1774        cache.insert(1, 10);
1775        // Hold read locks on all shards so try_write is blocked.
1776        let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect();
1777        assert!(cache.try_remove(&1).is_err());
1778        drop(guards);
1779        // Item must still be present since the remove did not happen.
1780        assert_eq!(cache.get(&1), Some(10));
1781    }
1782
1783    #[test]
1784    fn test_try_insert() {
1785        let cache = Cache::new(100);
1786
1787        assert_eq!(cache.try_insert(1, 10), Ok(()));
1788        assert_eq!(cache.get(&1), Some(10));
1789
1790        // Insert same key overwrites the previous value.
1791        assert_eq!(cache.try_insert(1, 20), Ok(()));
1792        assert_eq!(cache.get(&1), Some(20));
1793    }
1794
1795    #[test]
1796    fn test_try_insert_contended() {
1797        let cache = Cache::new(100);
1798        let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect();
1799        assert_eq!(cache.try_insert(1, 10), Err((1, 10)));
1800        drop(guards);
1801        assert_eq!(cache.get(&1), None);
1802    }
1803
1804    #[test]
1805    fn test_try_insert_with_lifecycle() {
1806        let cache = Cache::new(100);
1807        let mut lcs = Default::default();
1808
1809        // Successful insert records evictions into the provided request state.
1810        assert_eq!(cache.try_insert_with_lifecycle(1, 10, &mut lcs), Ok(()));
1811        assert_eq!(cache.get(&1), Some(10));
1812
1813        // The same request state can be threaded through several operations.
1814        assert_eq!(cache.try_insert_with_lifecycle(2, 20, &mut lcs), Ok(()));
1815        assert_eq!(cache.get(&2), Some(20));
1816
1817        // Contended when a read lock is held.
1818        let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect();
1819        assert_eq!(
1820            cache.try_insert_with_lifecycle(3, 30, &mut lcs),
1821            Err((3, 30))
1822        );
1823        drop(guards);
1824        assert_eq!(cache.get(&3), None);
1825    }
1826
1827    #[test]
1828    fn test_guard_leak() {
1829        let cache: Cache<i32, i32> = Cache::new(8);
1830        let guard1 = match cache.get_value_or_guard(&1, None) {
1831            GuardResult::Guard(g) => g,
1832            _ => panic!("expected guard"),
1833        };
1834        let idx1 = guard1.shared().idx();
1835        drop(guard1);
1836        let guard2 = match cache.get_value_or_guard(&1, None) {
1837            GuardResult::Guard(g) => g,
1838            _ => panic!("expected guard"),
1839        };
1840        let idx2 = guard2.shared().idx();
1841        drop(guard2);
1842        assert_eq!(idx1, idx2);
1843    }
1844
1845    // A real insert overwrites the placeholder in place, reusing its slab slot as
1846    // a Resident. Dropping the now-stale guard must not free that slot, otherwise
1847    // the live entry is evicted while the map still references it.
1848    #[test]
1849    fn test_guard_drop_after_overwrite_insert() {
1850        let cache: Cache<i32, i32> = Cache::new(8);
1851        let guard = match cache.get_value_or_guard(&1, None) {
1852            GuardResult::Guard(g) => g,
1853            _ => panic!("expected guard"),
1854        };
1855        cache.insert(1, 100);
1856        assert_eq!(cache.get(&1), Some(100));
1857        drop(guard);
1858        assert_eq!(cache.get(&1), Some(100));
1859    }
1860
1861    // A remove frees the placeholder's slab slot, which a later insert reuses for a
1862    // different key. Dropping the original guard must not free that slot again, or
1863    // it evicts the unrelated key.
1864    #[test]
1865    fn test_guard_drop_after_remove_and_reuse() {
1866        let cache: Cache<i32, i32> = Cache::new(8);
1867        let guard = match cache.get_value_or_guard(&1, None) {
1868            GuardResult::Guard(g) => g,
1869            _ => panic!("expected guard"),
1870        };
1871        cache.remove(&1);
1872        cache.insert(2, 222);
1873        assert_eq!(cache.get(&2), Some(222));
1874        drop(guard);
1875        assert_eq!(cache.get(&2), Some(222));
1876    }
1877}