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 std::{
962        sync::{Arc, Barrier},
963        thread,
964    };
965
966    #[test]
967    #[cfg_attr(miri, ignore)]
968    fn test_multiple_threads() {
969        const N_THREAD_PAIRS: usize = 8;
970        const N_ROUNDS: usize = 1_000;
971        const ITEMS_PER_THREAD: usize = 1_000;
972        let mut threads = Vec::new();
973        let barrier = Arc::new(Barrier::new(N_THREAD_PAIRS * 2));
974        let cache = Arc::new(Cache::new(N_THREAD_PAIRS * ITEMS_PER_THREAD / 10));
975        for t in 0..N_THREAD_PAIRS {
976            let barrier = barrier.clone();
977            let cache = cache.clone();
978            let handle = thread::spawn(move || {
979                let start = ITEMS_PER_THREAD * t;
980                barrier.wait();
981                for _round in 0..N_ROUNDS {
982                    for i in start..start + ITEMS_PER_THREAD {
983                        cache.insert(i, i);
984                    }
985                }
986            });
987            threads.push(handle);
988        }
989        for t in 0..N_THREAD_PAIRS {
990            let barrier = barrier.clone();
991            let cache = cache.clone();
992            let handle = thread::spawn(move || {
993                let start = ITEMS_PER_THREAD * t;
994                barrier.wait();
995                for _round in 0..N_ROUNDS {
996                    for i in start..start + ITEMS_PER_THREAD {
997                        if let Some(cached) = cache.get(&i) {
998                            assert_eq!(cached, i);
999                        }
1000                    }
1001                }
1002            });
1003            threads.push(handle);
1004        }
1005        for t in threads {
1006            t.join().unwrap();
1007        }
1008    }
1009
1010    #[test]
1011    fn test_iter() {
1012        let capacity = if cfg!(miri) { 100 } else { 100000 };
1013        let options = OptionsBuilder::new()
1014            .estimated_items_capacity(capacity)
1015            .weight_capacity(capacity as u64)
1016            .shards(2)
1017            .build()
1018            .unwrap();
1019        let cache = Cache::with_options(
1020            options,
1021            UnitWeighter,
1022            DefaultHashBuilder::default(),
1023            DefaultLifecycle::default(),
1024        );
1025        let items = capacity / 2;
1026        for i in 0..items {
1027            cache.insert(i, i);
1028        }
1029        assert_eq!(cache.len(), items);
1030        let mut iter_collected = cache.iter().collect::<Vec<_>>();
1031        assert_eq!(iter_collected.len(), items);
1032        iter_collected.sort();
1033        for (i, v) in iter_collected.into_iter().enumerate() {
1034            assert_eq!((i, i), v);
1035        }
1036    }
1037
1038    #[test]
1039    fn test_drain() {
1040        let capacity = if cfg!(miri) { 100 } else { 100000 };
1041        let options = OptionsBuilder::new()
1042            .estimated_items_capacity(capacity)
1043            .weight_capacity(capacity as u64)
1044            .shards(2)
1045            .build()
1046            .unwrap();
1047        let cache = Cache::with_options(
1048            options,
1049            UnitWeighter,
1050            DefaultHashBuilder::default(),
1051            DefaultLifecycle::default(),
1052        );
1053        let items = capacity / 2;
1054        for i in 0..items {
1055            cache.insert(i, i);
1056        }
1057        assert_eq!(cache.len(), items);
1058        let mut drain_collected = cache.drain().collect::<Vec<_>>();
1059        assert_eq!(cache.len(), 0);
1060        assert_eq!(drain_collected.len(), items);
1061        drain_collected.sort();
1062        for (i, v) in drain_collected.into_iter().enumerate() {
1063            assert_eq!((i, i), v);
1064        }
1065    }
1066
1067    #[test]
1068    fn test_set_capacity() {
1069        let cache = Cache::new(100);
1070        for i in 0..80 {
1071            cache.insert(i, i);
1072        }
1073        let initial_len = cache.len();
1074        assert!(initial_len <= 80);
1075
1076        // Set to smaller capacity
1077        cache.set_capacity(50);
1078        assert!(cache.len() <= 50);
1079        assert!(cache.weight() <= 50);
1080
1081        // Set to larger capacity
1082        cache.set_capacity(200);
1083        assert_eq!(cache.capacity(), 200);
1084
1085        // Insert more items
1086        for i in 100..180 {
1087            cache.insert(i, i);
1088        }
1089        assert!(cache.len() <= 180);
1090        assert!(cache.weight() <= 200);
1091    }
1092
1093    #[test]
1094    fn test_remove_if() {
1095        let cache = Cache::new(100);
1096
1097        // Insert test data
1098        cache.insert(1, 10);
1099        cache.insert(2, 20);
1100        cache.insert(3, 30);
1101
1102        // Test removing with predicate that returns true
1103        let removed = cache.remove_if(&2, |v| *v == 20);
1104        assert_eq!(removed, Some((2, 20)));
1105        assert_eq!(cache.get(&2), None);
1106
1107        // Test removing with predicate that returns false
1108        let not_removed = cache.remove_if(&3, |v| *v == 999);
1109        assert_eq!(not_removed, None);
1110        assert_eq!(cache.get(&3), Some(30));
1111
1112        // Test removing non-existent key
1113        let not_found = cache.remove_if(&999, |_| true);
1114        assert_eq!(not_found, None);
1115    }
1116
1117    /// Tests all basic entry actions: Retain, Remove, ReplaceWithGuard, Vacant, mutate+Retain
1118    #[test]
1119    fn test_entry_actions() {
1120        let cache = Cache::new(100);
1121        cache.insert(1, 10);
1122        cache.insert(2, 20);
1123
1124        // Retain returns the value via callback, entry stays
1125        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1126        assert!(matches!(result, EntryResult::Retained(10)));
1127        assert_eq!(cache.get(&1), Some(10));
1128
1129        // Mutate in place via Retain
1130        let result = cache.entry(&1, None, |_k, v| {
1131            *v += 5;
1132            EntryAction::Retain(())
1133        });
1134        assert!(matches!(result, EntryResult::Retained(())));
1135        assert_eq!(cache.get(&1), Some(15));
1136
1137        // Remove
1138        let result = cache.entry(&1, None, |_k, _v| EntryAction::<()>::Remove);
1139        assert!(matches!(result, EntryResult::Removed(1, 15)));
1140        assert_eq!(cache.get(&1), None);
1141
1142        // Remove then re-enter same key → Vacant
1143        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1144        match result {
1145            EntryResult::Vacant(g) => {
1146                let _ = g.insert(99);
1147                assert_eq!(cache.get(&1), Some(99));
1148            }
1149            _ => panic!("expected Vacant for removed key"),
1150        }
1151
1152        // ReplaceWithGuard: capture old value, get guard, insert new
1153        let mut old_val = 0;
1154        let result = cache.entry(&2, None, |_k, v| {
1155            old_val = *v;
1156            EntryAction::<()>::ReplaceWithGuard
1157        });
1158        assert_eq!(old_val, 20);
1159        match result {
1160            EntryResult::Replaced(g, old) => {
1161                assert_eq!(old, 20);
1162                let _ = g.insert(old_val + 100);
1163                assert_eq!(cache.get(&2), Some(120));
1164            }
1165            _ => panic!("expected Replaced"),
1166        }
1167
1168        // ReplaceWithGuard then abandon guard → entry gone
1169        let result = cache.entry(&2, None, |_k, _v| EntryAction::<()>::ReplaceWithGuard);
1170        match result {
1171            EntryResult::Replaced(g, _old) => {
1172                drop(g);
1173                assert_eq!(cache.get(&2), None);
1174            }
1175            _ => panic!("expected Replaced"),
1176        }
1177
1178        // Vacant key → guard
1179        let result = cache.entry(&3, None, |_k, v| EntryAction::Retain(*v));
1180        match result {
1181            EntryResult::Vacant(g) => {
1182                let _ = g.insert(30);
1183                assert_eq!(cache.get(&3), Some(30));
1184            }
1185            _ => panic!("expected Vacant"),
1186        }
1187    }
1188
1189    /// Tests weight tracking across all entry actions using a string-length weighter
1190    #[test]
1191    fn test_entry_weight_tracking() {
1192        #[derive(Clone)]
1193        struct StringWeighter;
1194        impl crate::Weighter<u64, String> for StringWeighter {
1195            fn weight(&self, _key: &u64, val: &String) -> u64 {
1196                val.len() as u64
1197            }
1198        }
1199
1200        let cache = Cache::with_weighter(100, 100_000, StringWeighter);
1201        cache.insert(1, "hello".to_string());
1202        cache.insert(2, "world".to_string());
1203        assert_eq!(cache.weight(), 10);
1204
1205        // Retain without mutation — weight unchanged
1206        let result = cache.entry(&1, None, |_k, _v| EntryAction::Retain(()));
1207        assert!(matches!(result, EntryResult::Retained(())));
1208        assert_eq!(cache.weight(), 10);
1209
1210        // Mutate to longer string — weight increases
1211        let result = cache.entry(&1, None, |_k, v| {
1212            v.push_str(" world");
1213            EntryAction::Retain(())
1214        });
1215        assert!(matches!(result, EntryResult::Retained(())));
1216        assert_eq!(cache.weight(), 16); // "hello world" (11) + "world" (5)
1217        assert_eq!(cache.get(&1).unwrap(), "hello world");
1218
1219        // Mutate to empty string — weight to zero, entry stays
1220        let result = cache.entry(&1, None, |_k, v| {
1221            v.clear();
1222            EntryAction::Retain(())
1223        });
1224        assert!(matches!(result, EntryResult::Retained(())));
1225        assert_eq!(cache.weight(), 5); // "" (0) + "world" (5)
1226        assert_eq!(cache.get(&1).unwrap(), "");
1227
1228        // Remove — weight decremented
1229        let result = cache.entry(&2, None, |_k, _v| EntryAction::<()>::Remove);
1230        assert!(matches!(result, EntryResult::Removed(2, _)));
1231        assert_eq!(cache.weight(), 0);
1232        assert_eq!(cache.len(), 1);
1233
1234        // ReplaceWithGuard — old weight gone, new weight after insert
1235        cache.insert(3, "hello".to_string());
1236        assert_eq!(cache.weight(), 5);
1237        let result = cache.entry(&3, None, |_k, _v| EntryAction::<()>::ReplaceWithGuard);
1238        match result {
1239            EntryResult::Replaced(g, _old) => {
1240                assert_eq!(cache.weight(), 0);
1241                let _ = g.insert("hello world!!".to_string());
1242                assert_eq!(cache.weight(), 13);
1243            }
1244            _ => panic!("expected Replaced"),
1245        }
1246    }
1247
1248    /// Tests eviction and zero-capacity edge cases
1249    #[test]
1250    fn test_entry_eviction() {
1251        // Cache with capacity for ~2 items — insert 3rd triggers eviction
1252        let cache = Cache::new(2);
1253        cache.insert(1, 10);
1254        cache.insert(2, 20);
1255        assert_eq!(cache.len(), 2);
1256
1257        let result = cache.entry(&3, None, |_k, v| EntryAction::Retain(*v));
1258        match result {
1259            EntryResult::Vacant(g) => {
1260                let _ = g.insert(30);
1261                assert!(cache.len() <= 2);
1262                assert_eq!(cache.get(&3), Some(30));
1263            }
1264            _ => panic!("expected Vacant"),
1265        }
1266
1267        // Zero-capacity cache — insert evicts immediately
1268        let cache = Cache::new(0);
1269        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1270        match result {
1271            EntryResult::Vacant(g) => {
1272                let _ = g.insert(10);
1273                assert_eq!(cache.get(&1), None);
1274            }
1275            _ => panic!("expected Vacant"),
1276        }
1277    }
1278
1279    /// Tests entry() waiting on existing placeholder: value arrives, guard abandoned
1280    #[test]
1281    #[cfg_attr(miri, ignore)]
1282    fn test_entry_concurrent_placeholder_wait() {
1283        let cache = Arc::new(Cache::new(100));
1284        let barrier = Arc::new(Barrier::new(2));
1285
1286        // Thread holds guard, inserts after delay
1287        let cache2 = cache.clone();
1288        let barrier2 = barrier.clone();
1289        let handle = thread::spawn(move || match cache2.get_value_or_guard(&1, None) {
1290            GuardResult::Guard(g) => {
1291                barrier2.wait();
1292                std::thread::sleep(Duration::from_millis(50));
1293                let _ = g.insert(42);
1294            }
1295            _ => panic!("expected guard"),
1296        });
1297
1298        barrier.wait();
1299        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1300        assert!(matches!(result, EntryResult::Retained(42)));
1301        handle.join().unwrap();
1302    }
1303
1304    /// Tests entry() getting guard when placeholder loader abandons
1305    #[test]
1306    #[cfg_attr(miri, ignore)]
1307    fn test_entry_concurrent_placeholder_guard_abandoned() {
1308        let cache = Arc::new(Cache::new(100));
1309        let barrier = Arc::new(Barrier::new(2));
1310
1311        let cache2 = cache.clone();
1312        let barrier2 = barrier.clone();
1313        let handle = thread::spawn(move || match cache2.get_value_or_guard(&1, None) {
1314            GuardResult::Guard(g) => {
1315                barrier2.wait();
1316                std::thread::sleep(Duration::from_millis(50));
1317                drop(g);
1318            }
1319            _ => panic!("expected guard"),
1320        });
1321
1322        barrier.wait();
1323        let result = cache.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1324        match result {
1325            EntryResult::Vacant(g) => {
1326                let _ = g.insert(99);
1327                assert_eq!(cache.get(&1), Some(99));
1328            }
1329            _ => panic!("expected Vacant after abandoned placeholder"),
1330        }
1331        handle.join().unwrap();
1332    }
1333
1334    /// Tests zero and nonzero timeouts
1335    #[test]
1336    #[cfg_attr(miri, ignore)]
1337    fn test_entry_timeout() {
1338        let cache = Cache::new(100);
1339
1340        // Zero timeout — immediate Timeout when placeholder exists
1341        let guard = match cache.get_value_or_guard(&1, None) {
1342            GuardResult::Guard(g) => g,
1343            _ => panic!("expected guard"),
1344        };
1345        let result = cache.entry(&1, Some(Duration::ZERO), |_k, v| EntryAction::Retain(*v));
1346        assert!(matches!(result, EntryResult::Timeout));
1347        let _ = guard.insert(1);
1348
1349        // Nonzero timeout — guard held longer than timeout
1350        let cache = Arc::new(Cache::new(100));
1351        let barrier = Arc::new(Barrier::new(2));
1352        let cache2 = cache.clone();
1353        let barrier2 = barrier.clone();
1354        let holder = thread::spawn(move || {
1355            let guard = match cache2.get_value_or_guard(&1, None) {
1356                GuardResult::Guard(g) => g,
1357                _ => panic!("expected guard"),
1358            };
1359            barrier2.wait();
1360            std::thread::sleep(Duration::from_millis(200));
1361            let _ = guard.insert(1);
1362        });
1363
1364        barrier.wait();
1365        let result = cache.entry(&1, Some(Duration::from_millis(50)), |_k, v| {
1366            EntryAction::Retain(*v)
1367        });
1368        assert!(matches!(result, EntryResult::Timeout));
1369        holder.join().unwrap();
1370    }
1371
1372    /// Tests multiple waiters all receiving the value
1373    #[test]
1374    #[cfg_attr(miri, ignore)]
1375    fn test_entry_concurrent_multiple_waiters() {
1376        let cache = Arc::new(Cache::new(100));
1377        let barrier = Arc::new(Barrier::new(4)); // 1 loader + 3 waiters
1378
1379        let cache1 = cache.clone();
1380        let barrier1 = barrier.clone();
1381        let loader = thread::spawn(move || match cache1.get_value_or_guard(&1, None) {
1382            GuardResult::Guard(g) => {
1383                barrier1.wait();
1384                std::thread::sleep(Duration::from_millis(50));
1385                let _ = g.insert(42);
1386            }
1387            _ => panic!("expected guard"),
1388        });
1389
1390        let mut waiters = Vec::new();
1391        for _ in 0..3 {
1392            let cache_c = cache.clone();
1393            let barrier_c = barrier.clone();
1394            waiters.push(thread::spawn(move || {
1395                barrier_c.wait();
1396                let result = cache_c.entry(&1, None, |_k, v| EntryAction::Retain(*v));
1397                match result {
1398                    EntryResult::Retained(v) => v,
1399                    _ => panic!("expected Value"),
1400                }
1401            }));
1402        }
1403
1404        loader.join().unwrap();
1405        for w in waiters {
1406            assert_eq!(w.join().unwrap(), 42);
1407        }
1408    }
1409
1410    /// Tests ReplaceWithGuard and Remove actions after waiting for a placeholder
1411    #[test]
1412    #[cfg_attr(miri, ignore)]
1413    fn test_entry_concurrent_action_after_wait() {
1414        // ReplaceWithGuard after wait
1415        let cache = Arc::new(Cache::new(100));
1416        let barrier = Arc::new(Barrier::new(2));
1417
1418        let cache1 = cache.clone();
1419        let barrier1 = barrier.clone();
1420        let loader = thread::spawn(move || match cache1.get_value_or_guard(&1, None) {
1421            GuardResult::Guard(g) => {
1422                barrier1.wait();
1423                std::thread::sleep(Duration::from_millis(50));
1424                let _ = g.insert(42);
1425            }
1426            _ => panic!("expected guard"),
1427        });
1428
1429        barrier.wait();
1430        let result = cache.entry(&1, None, |_k, _v| EntryAction::<()>::ReplaceWithGuard);
1431        match result {
1432            EntryResult::Replaced(g, old) => {
1433                assert_eq!(old, 42);
1434                let _ = g.insert(100);
1435                assert_eq!(cache.get(&1), Some(100));
1436            }
1437            _ => panic!("expected Replaced"),
1438        }
1439        loader.join().unwrap();
1440
1441        // Remove after wait
1442        let cache = Arc::new(Cache::new(100));
1443        let barrier = Arc::new(Barrier::new(2));
1444
1445        let cache1 = cache.clone();
1446        let barrier1 = barrier.clone();
1447        let loader = thread::spawn(move || match cache1.get_value_or_guard(&1, None) {
1448            GuardResult::Guard(g) => {
1449                barrier1.wait();
1450                std::thread::sleep(Duration::from_millis(50));
1451                let _ = g.insert(42);
1452            }
1453            _ => panic!("expected guard"),
1454        });
1455
1456        barrier.wait();
1457        let result = cache.entry(&1, None, |_k, _v| EntryAction::<()>::Remove);
1458        assert!(matches!(result, EntryResult::Removed(1, 42)));
1459        assert_eq!(cache.get(&1), None);
1460        loader.join().unwrap();
1461    }
1462
1463    /// Multi-thread stress test for entry()
1464    #[test]
1465    #[cfg_attr(miri, ignore)]
1466    fn test_entry_concurrent_stress() {
1467        const N_THREADS: usize = 8;
1468        const N_KEYS: usize = 50;
1469        const N_OPS: usize = 500;
1470
1471        let cache = Arc::new(Cache::new(1000));
1472        let barrier = Arc::new(Barrier::new(N_THREADS));
1473
1474        let mut handles = Vec::new();
1475        for t in 0..N_THREADS {
1476            let cache = cache.clone();
1477            let barrier = barrier.clone();
1478            handles.push(thread::spawn(move || {
1479                barrier.wait();
1480                for i in 0..N_OPS {
1481                    let key = (t * N_OPS + i) % N_KEYS;
1482                    let result = cache.entry(&key, Some(Duration::from_millis(10)), |_k, v| {
1483                        EntryAction::Retain(*v)
1484                    });
1485                    match result {
1486                        EntryResult::Retained(_) => {}
1487                        EntryResult::Vacant(g) => {
1488                            let _ = g.insert(key * 10);
1489                        }
1490                        EntryResult::Replaced(g, _) => {
1491                            let _ = g.insert(key * 10);
1492                        }
1493                        EntryResult::Timeout => {}
1494                        EntryResult::Removed(_, _) => {}
1495                    }
1496                }
1497            }));
1498        }
1499
1500        for h in handles {
1501            h.join().unwrap();
1502        }
1503
1504        assert!(cache.len() <= N_KEYS);
1505        for key in 0..N_KEYS {
1506            if let Some(v) = cache.get(&key) {
1507                assert_eq!(v, key * 10);
1508            }
1509        }
1510    }
1511
1512    // --- Async tests ---
1513
1514    /// Tests all basic async entry actions in one test
1515    #[tokio::test]
1516    async fn test_entry_async_actions() {
1517        let cache = Cache::new(100);
1518        cache.insert(1, 10);
1519        cache.insert(2, 20);
1520
1521        // Retain
1522        let result = cache.entry_async(&1, |_k, v| EntryAction::Retain(*v)).await;
1523        assert!(matches!(result, EntryResult::Retained(10)));
1524        assert_eq!(cache.get(&1), Some(10));
1525
1526        // Remove
1527        let result = cache
1528            .entry_async(&1, |_k, _v| EntryAction::<()>::Remove)
1529            .await;
1530        assert!(matches!(result, EntryResult::Removed(1, 10)));
1531        assert_eq!(cache.get(&1), None);
1532
1533        // ReplaceWithGuard
1534        let result = cache
1535            .entry_async(&2, |_k, _v| EntryAction::<()>::ReplaceWithGuard)
1536            .await;
1537        match result {
1538            EntryResult::Replaced(g, old) => {
1539                assert_eq!(old, 20);
1540                let _ = g.insert(42);
1541                assert_eq!(cache.get(&2), Some(42));
1542            }
1543            _ => panic!("expected Replaced"),
1544        }
1545
1546        // Vacant
1547        let result = cache.entry_async(&3, |_k, v| EntryAction::Retain(*v)).await;
1548        match result {
1549            EntryResult::Vacant(g) => {
1550                let _ = g.insert(99);
1551                assert_eq!(cache.get(&3), Some(99));
1552            }
1553            _ => panic!("expected Vacant"),
1554        }
1555    }
1556
1557    /// Tests async entry waiting on placeholder: value arrives, guard abandoned
1558    #[tokio::test(flavor = "multi_thread")]
1559    async fn test_entry_async_concurrent_wait() {
1560        let cache = Arc::new(Cache::new(100));
1561        let barrier = Arc::new(Barrier::new(2));
1562
1563        let cache1 = cache.clone();
1564        let barrier1 = barrier.clone();
1565        let holder = thread::spawn(move || {
1566            let guard = match cache1.get_value_or_guard(&1, None) {
1567                GuardResult::Guard(g) => g,
1568                _ => panic!("expected guard"),
1569            };
1570            barrier1.wait();
1571            std::thread::sleep(Duration::from_millis(50));
1572            let _ = guard.insert(42);
1573        });
1574
1575        barrier.wait();
1576        let result = cache.entry_async(&1, |_k, v| EntryAction::Retain(*v)).await;
1577        assert!(matches!(result, EntryResult::Retained(42)));
1578        holder.join().unwrap();
1579    }
1580
1581    /// Tests async entry getting guard when placeholder loader abandons
1582    #[tokio::test(flavor = "multi_thread")]
1583    async fn test_entry_async_concurrent_guard_abandoned() {
1584        let cache = Arc::new(Cache::new(100));
1585        let barrier = Arc::new(Barrier::new(2));
1586
1587        let cache1 = cache.clone();
1588        let barrier1 = barrier.clone();
1589        let holder = thread::spawn(move || {
1590            let guard = match cache1.get_value_or_guard(&1, None) {
1591                GuardResult::Guard(g) => g,
1592                _ => panic!("expected guard"),
1593            };
1594            barrier1.wait();
1595            std::thread::sleep(Duration::from_millis(50));
1596            drop(guard);
1597        });
1598
1599        barrier.wait();
1600        let result = cache.entry_async(&1, |_k, v| EntryAction::Retain(*v)).await;
1601        match result {
1602            EntryResult::Vacant(g) => {
1603                let _ = g.insert(99);
1604            }
1605            _ => panic!("expected Vacant after abandoned placeholder"),
1606        }
1607        assert_eq!(cache.get(&1), Some(99));
1608        holder.join().unwrap();
1609    }
1610
1611    /// Multi-task async stress test
1612    #[tokio::test(flavor = "multi_thread")]
1613    #[cfg_attr(miri, ignore)]
1614    async fn test_entry_async_concurrent_stress() {
1615        const N_TASKS: usize = 16;
1616        const N_KEYS: usize = 50;
1617        const N_OPS: usize = 200;
1618
1619        let cache = Arc::new(Cache::new(1000));
1620        let barrier = Arc::new(tokio::sync::Barrier::new(N_TASKS));
1621
1622        let mut handles = Vec::new();
1623        for t in 0..N_TASKS {
1624            let cache = cache.clone();
1625            let barrier = barrier.clone();
1626            handles.push(tokio::spawn(async move {
1627                barrier.wait().await;
1628                for i in 0..N_OPS {
1629                    let key = (t * N_OPS + i) % N_KEYS;
1630                    // Use get_or_insert_async instead of entry_async to avoid
1631                    // lifetime issues with tokio::spawn (entry_async borrows &self)
1632                    let _ = cache
1633                        .get_or_insert_async(&key, async { Ok::<_, ()>(key * 10) })
1634                        .await;
1635                }
1636            }));
1637        }
1638
1639        for h in handles {
1640            h.await.unwrap();
1641        }
1642
1643        assert!(cache.len() <= N_KEYS);
1644        for key in 0..N_KEYS {
1645            if let Some(v) = cache.get(&key) {
1646                assert_eq!(v, key * 10);
1647            }
1648        }
1649    }
1650
1651    // --- Non-blocking method tests ---
1652    #[test]
1653    fn test_try_contains_key() {
1654        let cache = Cache::new(100);
1655        cache.insert(1, 10);
1656
1657        assert!(cache.try_contains_key(&1).is_ok_and(|v| v));
1658        assert!(cache.try_contains_key(&2).is_ok_and(|v| !v));
1659    }
1660
1661    #[test]
1662    fn test_try_contains_key_contended() {
1663        let cache = Cache::new(100);
1664        cache.insert(1, 10);
1665        // Hold write locks on all shards so try_read is blocked.
1666        let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect();
1667        assert!(cache.try_contains_key(&1).is_err());
1668    }
1669
1670    #[test]
1671    fn test_try_get() {
1672        let cache = Cache::new(100);
1673        cache.insert(1, 10);
1674
1675        assert!(cache.try_get(&1).is_ok_and(|v| matches!(v, Some(10))));
1676        assert!(cache.try_get(&2).is_ok_and(|v| v.is_none()));
1677    }
1678
1679    #[test]
1680    fn test_try_get_contended() {
1681        let cache = Cache::new(100);
1682        cache.insert(1, 10);
1683        let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect();
1684        assert!(cache.try_get(&1).is_err());
1685    }
1686
1687    #[test]
1688    fn test_try_peek() {
1689        let cache = Cache::new(100);
1690        cache.insert(1, 10);
1691
1692        assert!(cache.try_peek(&1).is_ok_and(|v| matches!(v, Some(10))));
1693        assert!(cache.try_peek(&2).is_ok_and(|v| v.is_none()));
1694    }
1695
1696    #[test]
1697    fn test_try_peek_contended() {
1698        let cache = Cache::new(100);
1699        cache.insert(1, 10);
1700        let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect();
1701        assert!(cache.try_peek(&1).is_err());
1702    }
1703
1704    #[test]
1705    fn test_try_remove() {
1706        let cache = Cache::new(100);
1707        cache.insert(1, 10);
1708
1709        assert!(cache
1710            .try_remove(&1)
1711            .is_ok_and(|v| matches!(v, Some((1, 10)))));
1712        assert!(cache.try_remove(&1).is_ok_and(|v| v.is_none()));
1713        assert!(cache.try_remove(&99).is_ok_and(|v| v.is_none()));
1714    }
1715
1716    #[test]
1717    fn test_try_remove_contended() {
1718        let cache = Cache::new(100);
1719        cache.insert(1, 10);
1720        // Hold read locks on all shards so try_write is blocked.
1721        let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect();
1722        assert!(cache.try_remove(&1).is_err());
1723        drop(guards);
1724        // Item must still be present since the remove did not happen.
1725        assert_eq!(cache.get(&1), Some(10));
1726    }
1727
1728    #[test]
1729    fn test_try_insert() {
1730        let cache = Cache::new(100);
1731
1732        assert_eq!(cache.try_insert(1, 10), Ok(()));
1733        assert_eq!(cache.get(&1), Some(10));
1734
1735        // Insert same key overwrites the previous value.
1736        assert_eq!(cache.try_insert(1, 20), Ok(()));
1737        assert_eq!(cache.get(&1), Some(20));
1738    }
1739
1740    #[test]
1741    fn test_try_insert_contended() {
1742        let cache = Cache::new(100);
1743        let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect();
1744        assert_eq!(cache.try_insert(1, 10), Err((1, 10)));
1745        drop(guards);
1746        assert_eq!(cache.get(&1), None);
1747    }
1748
1749    #[test]
1750    fn test_try_insert_with_lifecycle() {
1751        let cache = Cache::new(100);
1752
1753        // Successful insert returns the lifecycle request state.
1754        let result = cache.try_insert_with_lifecycle(1, 10);
1755        assert!(result.is_ok());
1756        let lcs = result.ok().unwrap();
1757        cache.lifecycle.end_request(lcs);
1758        assert_eq!(cache.get(&1), Some(10));
1759
1760        // Contended when a read lock is held.
1761        let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect();
1762        assert_eq!(cache.try_insert_with_lifecycle(2, 20), Err((2, 20)));
1763        drop(guards);
1764        assert_eq!(cache.get(&2), None);
1765    }
1766}