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