sieve_cache/
sync.rs

1use crate::SieveCache;
2use std::borrow::Borrow;
3use std::fmt;
4use std::hash::Hash;
5use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
6
7/// A thread-safe wrapper around `SieveCache`.
8///
9/// This provides a thread-safe implementation of the SIEVE cache algorithm by wrapping
10/// the standard `SieveCache` in an `Arc<Mutex<>>`. It offers the same functionality as
11/// the underlying cache but with thread safety guarantees.
12///
13/// # Thread Safety
14///
15/// All operations acquire a lock on the entire cache, which provides strong consistency
16/// but may become a bottleneck under high contention. For workloads with high concurrency,
17/// consider using [`ShardedSieveCache`](crate::ShardedSieveCache) instead, which partitions
18/// the cache into multiple independently-locked segments.
19///
20/// ## Lock Behavior
21///
22/// The thread safety mechanism works as follows:
23///
24/// - Simple query operations (e.g., `get`, `contains_key`) hold the lock only long enough to
25///   read and clone the value
26/// - Modification operations (e.g., `insert`, `remove`) hold the lock for the duration of the change
27/// - Operations that accept callbacks have specific lock behavior:
28///   - `get_mut` acquires and releases the lock repeatedly to avoid deadlocks, using a clone-modify-update pattern
29///   - `for_each_value`, `for_each_entry`, and `retain` collect data under the lock, then
30///     release it before processing to avoid blocking other threads
31///
32/// ## Deadlock Prevention
33///
34/// This implementation prevents deadlocks by:
35///
36/// - Never allowing callbacks to execute while holding the cache lock
37/// - Using a clone-modify-update pattern for all callbacks that need to modify values
38/// - Ensuring lock acquisition is always done in a consistent order
39/// - Providing explicit transaction methods that make locking transparent to the user
40///
41/// ## Consistency Guarantees
42///
43/// - Operations on individual keys are atomic and isolated
44/// - Snapshot-based operations (e.g., iteration, bulk operations) may not reflect
45///   concurrent modifications
46/// - When using callback functions, be aware they execute outside the lock which means
47///   the cache state may change between lock acquisitions
48///
49/// # Examples
50///
51/// ```
52/// # use sieve_cache::SyncSieveCache;
53/// let cache = SyncSieveCache::new(100).unwrap();
54///
55/// // Multiple threads can safely access the cache
56/// cache.insert("key1".to_string(), "value1".to_string());
57/// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
58/// ```
59///
60/// Example with callbacks:
61///
62/// ```
63/// # use sieve_cache::SyncSieveCache;
64/// # use std::thread;
65/// let cache = SyncSieveCache::new(100).unwrap();
66/// cache.insert("key1".to_string(), 1);
67/// cache.insert("key2".to_string(), 2);
68///
69/// // Create a clone to move into another thread
70/// let cache_clone = cache.clone();
71///
72/// // Spawn a thread that modifies values
73/// let handle = thread::spawn(move || {
74///     // The cache is safely accessible from multiple threads
75///     cache_clone.for_each_value(|value| {
76///         *value += 10;  // Add 10 to each value
77///     });
78/// });
79///
80/// // Wait for the background thread to complete
81/// handle.join().unwrap();
82///
83/// // Values have been updated
84/// assert_eq!(cache.get(&"key1".to_string()), Some(11));
85/// assert_eq!(cache.get(&"key2".to_string()), Some(12));
86/// ```
87#[derive(Clone)]
88pub struct SyncSieveCache<K, V>
89where
90    K: Eq + Hash + Clone + Send + Sync,
91    V: Send + Sync,
92{
93    inner: Arc<Mutex<SieveCache<K, V>>>,
94}
95
96impl<K, V> Default for SyncSieveCache<K, V>
97where
98    K: Eq + Hash + Clone + Send + Sync,
99    V: Send + Sync,
100{
101    /// Creates a new cache with a default capacity of 100 entries.
102    ///
103    /// # Panics
104    ///
105    /// Panics if the underlying `SieveCache::new()` returns an error, which should never
106    /// happen for a non-zero capacity.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// # use sieve_cache::SyncSieveCache;
112    /// # use std::default::Default;
113    /// let cache: SyncSieveCache<String, u32> = Default::default();
114    /// assert_eq!(cache.capacity(), 100);
115    /// ```
116    fn default() -> Self {
117        Self::new(100).expect("Failed to create cache with default capacity")
118    }
119}
120
121impl<K, V> fmt::Debug for SyncSieveCache<K, V>
122where
123    K: Eq + Hash + Clone + Send + Sync + fmt::Debug,
124    V: Send + Sync + fmt::Debug,
125{
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        let guard = self.locked_cache();
128        f.debug_struct("SyncSieveCache")
129            .field("capacity", &guard.capacity())
130            .field("len", &guard.len())
131            .finish()
132    }
133}
134
135impl<K, V> From<SieveCache<K, V>> for SyncSieveCache<K, V>
136where
137    K: Eq + Hash + Clone + Send + Sync,
138    V: Send + Sync,
139{
140    /// Creates a new thread-safe cache from an existing `SieveCache`.
141    ///
142    /// This allows for easily converting a single-threaded cache to a thread-safe version.
143    ///
144    /// # Examples
145    ///
146    /// ```
147    /// # use sieve_cache::{SieveCache, SyncSieveCache};
148    /// let mut single_threaded = SieveCache::new(100).unwrap();
149    /// single_threaded.insert("key".to_string(), "value".to_string());
150    ///
151    /// // Convert to thread-safe version
152    /// let thread_safe = SyncSieveCache::from(single_threaded);
153    /// assert_eq!(thread_safe.get(&"key".to_string()), Some("value".to_string()));
154    /// ```
155    fn from(cache: SieveCache<K, V>) -> Self {
156        Self {
157            inner: Arc::new(Mutex::new(cache)),
158        }
159    }
160}
161
162impl<K, V> IntoIterator for SyncSieveCache<K, V>
163where
164    K: Eq + Hash + Clone + Send + Sync,
165    V: Clone + Send + Sync,
166{
167    type Item = (K, V);
168    type IntoIter = std::vec::IntoIter<(K, V)>;
169
170    /// Converts the cache into an iterator over its key-value pairs.
171    ///
172    /// This collects all entries into a Vec and returns an iterator over that Vec.
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// # use sieve_cache::SyncSieveCache;
178    /// # use std::collections::HashMap;
179    /// let cache = SyncSieveCache::new(100).unwrap();
180    /// cache.insert("key1".to_string(), "value1".to_string());
181    /// cache.insert("key2".to_string(), "value2".to_string());
182    ///
183    /// // Collect into a HashMap
184    /// let map: HashMap<_, _> = cache.into_iter().collect();
185    /// assert_eq!(map.len(), 2);
186    /// assert_eq!(map.get("key1"), Some(&"value1".to_string()));
187    /// ```
188    fn into_iter(self) -> Self::IntoIter {
189        self.entries().into_iter()
190    }
191}
192
193impl<K, V> SyncSieveCache<K, V>
194where
195    K: Eq + Hash + Clone + Send + Sync,
196    V: Send + Sync,
197{
198    /// Creates a new thread-safe cache with the given capacity.
199    ///
200    /// # Errors
201    ///
202    /// Returns an error if the capacity is zero.
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// # use sieve_cache::SyncSieveCache;
208    /// let cache = SyncSieveCache::<String, String>::new(100).unwrap();
209    /// ```
210    pub fn new(capacity: usize) -> Result<Self, &'static str> {
211        let cache = SieveCache::new(capacity)?;
212        Ok(Self {
213            inner: Arc::new(Mutex::new(cache)),
214        })
215    }
216
217    /// Returns a locked reference to the underlying cache.
218    ///
219    /// This is an internal helper method to abstract away the lock handling.
220    /// If the mutex is poisoned due to a panic in another thread, the poison
221    /// error is recovered from by calling `into_inner()` to access the underlying data.
222    #[inline]
223    fn locked_cache(&self) -> MutexGuard<'_, SieveCache<K, V>> {
224        self.inner.lock().unwrap_or_else(PoisonError::into_inner)
225    }
226
227    /// Returns the capacity of the cache.
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// # use sieve_cache::SyncSieveCache;
233    /// let cache = SyncSieveCache::<String, u32>::new(100).unwrap();
234    /// assert_eq!(cache.capacity(), 100);
235    /// ```
236    pub fn capacity(&self) -> usize {
237        self.locked_cache().capacity()
238    }
239
240    /// Returns the number of cached values.
241    ///
242    /// # Examples
243    ///
244    /// ```
245    /// # use sieve_cache::SyncSieveCache;
246    /// let cache = SyncSieveCache::new(100).unwrap();
247    /// cache.insert("key".to_string(), "value".to_string());
248    /// assert_eq!(cache.len(), 1);
249    /// ```
250    pub fn len(&self) -> usize {
251        self.locked_cache().len()
252    }
253
254    /// Returns `true` when no values are currently cached.
255    ///
256    /// # Examples
257    ///
258    /// ```
259    /// # use sieve_cache::SyncSieveCache;
260    /// let cache = SyncSieveCache::<String, String>::new(100).unwrap();
261    /// assert!(cache.is_empty());
262    ///
263    /// cache.insert("key".to_string(), "value".to_string());
264    /// assert!(!cache.is_empty());
265    /// ```
266    pub fn is_empty(&self) -> bool {
267        self.locked_cache().is_empty()
268    }
269
270    /// Returns `true` if there is a value in the cache mapped to by `key`.
271    ///
272    /// # Examples
273    ///
274    /// ```
275    /// # use sieve_cache::SyncSieveCache;
276    /// let cache = SyncSieveCache::new(100).unwrap();
277    /// cache.insert("key".to_string(), "value".to_string());
278    ///
279    /// assert!(cache.contains_key(&"key".to_string()));
280    /// assert!(!cache.contains_key(&"missing".to_string()));
281    /// ```
282    pub fn contains_key<Q>(&self, key: &Q) -> bool
283    where
284        Q: Hash + Eq + ?Sized,
285        K: Borrow<Q>,
286    {
287        let guard = self.locked_cache();
288        guard.contains_key(key)
289    }
290
291    /// Gets a clone of the value in the cache mapped to by `key`.
292    ///
293    /// If no value exists for `key`, this returns `None`.
294    ///
295    /// # Note
296    ///
297    /// Unlike the unwrapped `SieveCache`, this returns a clone of the value
298    /// rather than a reference, since the mutex guard would be dropped after
299    /// this method returns. This means that `V` must implement `Clone`.
300    ///
301    /// # Examples
302    ///
303    /// ```
304    /// # use sieve_cache::SyncSieveCache;
305    /// let cache = SyncSieveCache::new(100).unwrap();
306    /// cache.insert("key".to_string(), "value".to_string());
307    ///
308    /// assert_eq!(cache.get(&"key".to_string()), Some("value".to_string()));
309    /// assert_eq!(cache.get(&"missing".to_string()), None);
310    /// ```
311    pub fn get<Q>(&self, key: &Q) -> Option<V>
312    where
313        Q: Hash + Eq + ?Sized,
314        K: Borrow<Q>,
315        V: Clone,
316    {
317        let mut guard = self.locked_cache();
318        guard.get(key).cloned()
319    }
320
321    /// Gets a mutable reference to the value in the cache mapped to by `key` via a callback function.
322    ///
323    /// If no value exists for `key`, the callback will not be invoked and this returns `false`.
324    /// Otherwise, the callback is invoked with a mutable reference to the value and this returns `true`.
325    ///
326    /// This operation marks the entry as "visited" in the SIEVE algorithm,
327    /// which affects eviction decisions.
328    ///
329    /// # Thread Safety
330    ///
331    /// This method operates safely with recursive calls by:
332    ///
333    /// 1. Cloning the value with a short-lived lock
334    /// 2. Releasing the lock during callback execution
335    /// 3. Re-acquiring the lock to update the original value
336    ///
337    /// This approach means:
338    ///
339    /// - The callback can safely make other calls to the same cache instance
340    /// - The value can be modified by other threads during the callback execution
341    /// - Changes are not visible to other threads until the callback completes
342    /// - Last writer wins if multiple threads modify the same key concurrently
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// # use sieve_cache::SyncSieveCache;
348    /// let cache = SyncSieveCache::new(100).unwrap();
349    /// cache.insert("key".to_string(), "value".to_string());
350    ///
351    /// // Modify the value in-place
352    /// cache.get_mut(&"key".to_string(), |value| {
353    ///     *value = "new_value".to_string();
354    /// });
355    ///
356    /// assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
357    /// ```
358    pub fn get_mut<Q, F>(&self, key: &Q, f: F) -> bool
359    where
360        Q: Hash + Eq + ?Sized,
361        K: Borrow<Q>,
362        F: FnOnce(&mut V),
363        V: Clone,
364    {
365        // Get a clone of the value if it exists, to avoid deadlocks
366        // if the callback tries to use other methods on this cache
367        let value_opt = {
368            let mut guard = self.locked_cache();
369            // Clone the value before releasing the lock
370            guard.get_mut(key).map(|v| v.clone())
371        };
372
373        if let Some(mut value) = value_opt {
374            // Execute the callback on the cloned value without holding the lock
375            f(&mut value);
376
377            // Update the value back to the cache
378            let mut guard = self.locked_cache();
379            if let Some(original) = guard.get_mut(key) {
380                *original = value;
381                true
382            } else {
383                // Key was removed while callback was executing
384                false
385            }
386        } else {
387            false
388        }
389    }
390
391    /// Maps `key` to `value` in the cache, possibly evicting old entries.
392    ///
393    /// This method returns `true` when this is a new entry, and `false` if an existing entry was
394    /// updated.
395    ///
396    /// # Examples
397    ///
398    /// ```
399    /// # use sieve_cache::SyncSieveCache;
400    /// let cache = SyncSieveCache::new(100).unwrap();
401    ///
402    /// // Insert a new key
403    /// assert!(cache.insert("key1".to_string(), "value1".to_string()));
404    ///
405    /// // Update an existing key
406    /// assert!(!cache.insert("key1".to_string(), "updated_value".to_string()));
407    /// ```
408    pub fn insert(&self, key: K, value: V) -> bool {
409        let mut guard = self.locked_cache();
410        guard.insert(key, value)
411    }
412
413    /// Removes the cache entry mapped to by `key`.
414    ///
415    /// This method returns the value removed from the cache. If `key` did not map to any value,
416    /// then this returns `None`.
417    ///
418    /// # Examples
419    ///
420    /// ```
421    /// # use sieve_cache::SyncSieveCache;
422    /// let cache = SyncSieveCache::new(100).unwrap();
423    /// cache.insert("key".to_string(), "value".to_string());
424    ///
425    /// // Remove an existing key
426    /// assert_eq!(cache.remove(&"key".to_string()), Some("value".to_string()));
427    ///
428    /// // Attempt to remove a missing key
429    /// assert_eq!(cache.remove(&"key".to_string()), None);
430    /// ```
431    pub fn remove<Q>(&self, key: &Q) -> Option<V>
432    where
433        K: Borrow<Q>,
434        Q: Eq + Hash + ?Sized,
435    {
436        let mut guard = self.locked_cache();
437        guard.remove(key)
438    }
439
440    /// Removes and returns a value from the cache that was not recently accessed.
441    ///
442    /// This implements the SIEVE eviction algorithm to select an entry for removal.
443    /// If no suitable value exists, this returns `None`.
444    ///
445    /// # Examples
446    ///
447    /// ```
448    /// # use sieve_cache::SyncSieveCache;
449    /// let cache = SyncSieveCache::new(2).unwrap();
450    /// cache.insert("key1".to_string(), "value1".to_string());
451    /// cache.insert("key2".to_string(), "value2".to_string());
452    ///
453    /// // Accessing key1 marks it as recently used
454    /// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
455    ///
456    /// // Insert a new key, which should evict key2
457    /// cache.insert("key3".to_string(), "value3".to_string());
458    ///
459    /// // key2 should have been evicted
460    /// assert_eq!(cache.get(&"key2".to_string()), None);
461    /// ```
462    pub fn evict(&self) -> Option<V> {
463        let mut guard = self.locked_cache();
464        guard.evict()
465    }
466
467    /// Removes all entries from the cache.
468    ///
469    /// This operation clears all stored values and resets the cache to an empty state,
470    /// while maintaining the original capacity.
471    ///
472    /// # Examples
473    ///
474    /// ```
475    /// # use sieve_cache::SyncSieveCache;
476    /// let cache = SyncSieveCache::new(100).unwrap();
477    /// cache.insert("key1".to_string(), "value1".to_string());
478    /// cache.insert("key2".to_string(), "value2".to_string());
479    ///
480    /// assert_eq!(cache.len(), 2);
481    ///
482    /// cache.clear();
483    /// assert_eq!(cache.len(), 0);
484    /// assert!(cache.is_empty());
485    /// ```
486    pub fn clear(&self) {
487        let mut guard = self.locked_cache();
488        guard.clear();
489    }
490
491    /// Returns an iterator over all keys in the cache.
492    ///
493    /// The order of keys is not specified and should not be relied upon.
494    ///
495    /// # Examples
496    ///
497    /// ```
498    /// # use sieve_cache::SyncSieveCache;
499    /// # use std::collections::HashSet;
500    /// let cache = SyncSieveCache::new(100).unwrap();
501    /// cache.insert("key1".to_string(), "value1".to_string());
502    /// cache.insert("key2".to_string(), "value2".to_string());
503    ///
504    /// let keys: HashSet<_> = cache.keys().into_iter().collect();
505    /// assert_eq!(keys.len(), 2);
506    /// assert!(keys.contains(&"key1".to_string()));
507    /// assert!(keys.contains(&"key2".to_string()));
508    /// ```
509    pub fn keys(&self) -> Vec<K> {
510        let guard = self.locked_cache();
511        guard.keys().cloned().collect()
512    }
513
514    /// Returns all values in the cache.
515    ///
516    /// The order of values is not specified and should not be relied upon.
517    ///
518    /// # Examples
519    ///
520    /// ```
521    /// # use sieve_cache::SyncSieveCache;
522    /// # use std::collections::HashSet;
523    /// let cache = SyncSieveCache::new(100).unwrap();
524    /// cache.insert("key1".to_string(), "value1".to_string());
525    /// cache.insert("key2".to_string(), "value2".to_string());
526    ///
527    /// let values: HashSet<_> = cache.values().into_iter().collect();
528    /// assert_eq!(values.len(), 2);
529    /// assert!(values.contains(&"value1".to_string()));
530    /// assert!(values.contains(&"value2".to_string()));
531    /// ```
532    pub fn values(&self) -> Vec<V>
533    where
534        V: Clone,
535    {
536        let guard = self.locked_cache();
537        guard.values().cloned().collect()
538    }
539
540    /// Returns all key-value pairs in the cache.
541    ///
542    /// The order of pairs is not specified and should not be relied upon.
543    ///
544    /// # Examples
545    ///
546    /// ```
547    /// # use sieve_cache::SyncSieveCache;
548    /// # use std::collections::HashMap;
549    /// let cache = SyncSieveCache::new(100).unwrap();
550    /// cache.insert("key1".to_string(), "value1".to_string());
551    /// cache.insert("key2".to_string(), "value2".to_string());
552    ///
553    /// let entries: HashMap<_, _> = cache.entries().into_iter().collect();
554    /// assert_eq!(entries.len(), 2);
555    /// assert_eq!(entries.get(&"key1".to_string()), Some(&"value1".to_string()));
556    /// assert_eq!(entries.get(&"key2".to_string()), Some(&"value2".to_string()));
557    /// ```
558    pub fn entries(&self) -> Vec<(K, V)>
559    where
560        V: Clone,
561    {
562        let guard = self.locked_cache();
563        guard.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
564    }
565
566    /// Applies a function to all values in the cache.
567    ///
568    /// This method safely processes values by collecting them with the lock held,
569    /// then releasing the lock before applying the function to each value individually.
570    /// If the function modifies the values, the changes are saved back to the cache.
571    ///
572    /// # Thread Safety
573    ///
574    /// This method operates in three phases:
575    /// 1. It acquires the lock and creates a complete snapshot of the cache
576    /// 2. It releases the lock and applies the callback to each value
577    /// 3. It acquires the lock again individually for each value when writing changes back
578    ///
579    /// This approach means:
580    /// - The lock is not held during callback execution, preventing lock contention
581    /// - If other threads modify the cache between steps 1 and 3, those changes might be overwritten
582    /// - The callback sees a point-in-time snapshot that might not reflect the latest state
583    /// - For long-running operations, consider using individual key operations instead
584    ///
585    /// # Examples
586    ///
587    /// ```
588    /// # use sieve_cache::SyncSieveCache;
589    /// let cache = SyncSieveCache::new(100).unwrap();
590    /// cache.insert("key1".to_string(), "value1".to_string());
591    /// cache.insert("key2".to_string(), "value2".to_string());
592    ///
593    /// // Update all values by appending text
594    /// cache.for_each_value(|value| {
595    ///     *value = format!("{}_updated", value);
596    /// });
597    ///
598    /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_updated".to_string()));
599    /// assert_eq!(cache.get(&"key2".to_string()), Some("value2_updated".to_string()));
600    /// ```
601    pub fn for_each_value<F>(&self, mut f: F)
602    where
603        F: FnMut(&mut V),
604        V: Clone,
605    {
606        // First, safely collect all keys and values while holding the lock
607        let entries = self.with_lock(|inner_cache| {
608            inner_cache
609                .iter()
610                .map(|(k, v)| (k.clone(), v.clone()))
611                .collect::<Vec<(K, V)>>()
612        });
613
614        // Process each value outside the lock
615        let mut updated_entries = Vec::new();
616        for (key, mut value) in entries {
617            f(&mut value);
618            updated_entries.push((key, value));
619        }
620
621        // Update any changed values back to the cache
622        for (key, value) in updated_entries {
623            self.insert(key, value);
624        }
625    }
626
627    /// Applies a function to all key-value pairs in the cache.
628    ///
629    /// This method safely processes key-value pairs by collecting them with the lock held,
630    /// then releasing the lock before applying the function to each pair individually.
631    /// If the function modifies the values, the changes are saved back to the cache.
632    ///
633    /// # Thread Safety
634    ///
635    /// This method operates in three phases:
636    /// 1. It acquires the lock and creates a complete snapshot of the cache
637    /// 2. It releases the lock and applies the callback to each key-value pair
638    /// 3. It acquires the lock again individually for each entry when writing changes back
639    ///
640    /// This approach means:
641    /// - The lock is not held during callback execution, preventing lock contention
642    /// - If other threads modify the cache between steps 1 and 3, those changes might be overwritten
643    /// - The callback sees a point-in-time snapshot that might not reflect the latest state
644    /// - For long-running operations, consider using individual key operations instead
645    ///
646    /// # Examples
647    ///
648    /// ```
649    /// # use sieve_cache::SyncSieveCache;
650    /// let cache = SyncSieveCache::new(100).unwrap();
651    /// cache.insert("key1".to_string(), "value1".to_string());
652    /// cache.insert("key2".to_string(), "value2".to_string());
653    ///
654    /// // Update all values associated with keys containing '1'
655    /// cache.for_each_entry(|(key, value)| {
656    ///     if key.contains('1') {
657    ///         *value = format!("{}_special", value);
658    ///     }
659    /// });
660    ///
661    /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_special".to_string()));
662    /// assert_eq!(cache.get(&"key2".to_string()), Some("value2".to_string()));
663    /// ```
664    pub fn for_each_entry<F>(&self, mut f: F)
665    where
666        F: FnMut((&K, &mut V)),
667        V: Clone,
668    {
669        // First, safely collect all keys and values while holding the lock
670        let entries = self.with_lock(|inner_cache| {
671            inner_cache
672                .iter()
673                .map(|(k, v)| (k.clone(), v.clone()))
674                .collect::<Vec<(K, V)>>()
675        });
676
677        // Process each entry outside the lock
678        let mut updated_entries = Vec::new();
679        for (key, mut value) in entries {
680            let key_ref = &key;
681            f((key_ref, &mut value));
682            updated_entries.push((key, value));
683        }
684
685        // Update any changed values back to the cache
686        for (key, value) in updated_entries {
687            self.insert(key, value);
688        }
689    }
690
691    /// Gets exclusive access to the underlying cache to perform multiple operations atomically.
692    ///
693    /// This is useful when you need to perform a series of operations that depend on each other
694    /// and you want to ensure that no other thread can access the cache between operations.
695    ///
696    /// # Thread Safety
697    ///
698    /// This method provides the strongest thread safety guarantees by:
699    ///
700    /// - Acquiring the lock once for the entire duration of the callback
701    /// - Allowing multiple operations to be performed atomically within a single lock scope
702    /// - Ensuring all operations within the callback are fully isolated from other threads
703    ///
704    /// However, this also means:
705    ///
706    /// - The entire cache is locked during the callback execution
707    /// - Other threads will be blocked from accessing the cache until the callback completes
708    /// - Long-running operations within the callback will cause high contention
709    /// - Callbacks must never try to access the same cache instance recursively (deadlock risk)
710    ///
711    /// This method should be used when you need atomic multi-step operations but used
712    /// sparingly in high-concurrency environments.
713    ///
714    /// # Examples
715    ///
716    /// ```
717    /// # use sieve_cache::SyncSieveCache;
718    /// let cache = SyncSieveCache::new(100).unwrap();
719    ///
720    /// cache.with_lock(|inner_cache| {
721    ///     // Perform multiple operations atomically
722    ///     inner_cache.insert("key1".to_string(), "value1".to_string());
723    ///     inner_cache.insert("key2".to_string(), "value2".to_string());
724    ///
725    ///     // We can check internal state mid-transaction
726    ///     assert_eq!(inner_cache.len(), 2);
727    /// });
728    /// ```
729    pub fn with_lock<F, T>(&self, f: F) -> T
730    where
731        F: FnOnce(&mut SieveCache<K, V>) -> T,
732    {
733        let mut guard = self.locked_cache();
734        f(&mut guard)
735    }
736
737    /// Retains only the elements specified by the predicate.
738    ///
739    /// Removes all entries for which the provided function returns `false`.
740    /// The elements are visited in arbitrary, unspecified order.
741    ///
742    /// This method offers two modes of operation:
743    /// - Default mode: evaluates predicates outside the lock, with separate remove operations
744    /// - Batch mode: evaluates predicates outside the lock, then performs removals in a single batch
745    ///
746    /// # Thread Safety
747    ///
748    /// This method operates in three phases:
749    /// 1. It acquires the lock and creates a complete snapshot of the cache
750    /// 2. It releases the lock and applies the predicate to each entry
751    /// 3. It either:
752    ///    - Acquires the lock again individually for each entry to be removed (default), or
753    ///    - Acquires the lock once and removes all entries in a batch (batch mode)
754    ///
755    /// This approach means:
756    /// - The lock is not held during predicate execution, preventing lock contention
757    /// - If other threads modify the cache between steps 1 and 3, the method may:
758    ///   - Remove entries that were modified and would now pass the predicate
759    ///   - Miss removing entries added after the snapshot was taken
760    /// - The predicate sees a point-in-time snapshot that might not reflect the latest state
761    /// - For strict consistency requirements, use `with_lock` instead
762    ///
763    /// # Examples
764    ///
765    /// Basic usage:
766    ///
767    /// ```
768    /// # use sieve_cache::SyncSieveCache;
769    /// let cache = SyncSieveCache::new(100).unwrap();
770    /// cache.insert("key1".to_string(), 100);
771    /// cache.insert("key2".to_string(), 200);
772    /// cache.insert("key3".to_string(), 300);
773    ///
774    /// // Keep only entries with values greater than 150
775    /// cache.retain(|_, value| *value > 150);
776    ///
777    /// assert_eq!(cache.len(), 2);
778    /// assert!(!cache.contains_key(&"key1".to_string()));
779    /// assert!(cache.contains_key(&"key2".to_string()));
780    /// assert!(cache.contains_key(&"key3".to_string()));
781    /// ```
782    ///
783    /// Using batch mode (more efficient):
784    ///
785    /// ```
786    /// # use sieve_cache::SyncSieveCache;
787    /// let cache = SyncSieveCache::new(100).unwrap();
788    /// cache.insert("key1".to_string(), 100);
789    /// cache.insert("key2".to_string(), 200);
790    /// cache.insert("key3".to_string(), 300);
791    ///
792    /// // Keep only entries with values greater than 150 (batch mode)
793    /// cache.retain_batch(|_, value| *value > 150);
794    ///
795    /// assert_eq!(cache.len(), 2);
796    /// assert!(!cache.contains_key(&"key1".to_string()));
797    /// assert!(cache.contains_key(&"key2".to_string()));
798    /// assert!(cache.contains_key(&"key3".to_string()));
799    /// ```
800    pub fn retain<F>(&self, mut f: F)
801    where
802        F: FnMut(&K, &V) -> bool,
803        V: Clone,
804    {
805        // First, safely collect all entries while holding the lock
806        let entries = self.with_lock(|inner_cache| {
807            inner_cache
808                .iter()
809                .map(|(k, v)| (k.clone(), v.clone()))
810                .collect::<Vec<(K, V)>>()
811        });
812
813        // Now check each entry against the predicate without holding the lock
814        for (key, value) in entries {
815            if !f(&key, &value) {
816                // Remove entries that don't match the predicate
817                // This acquires the lock for each removal operation
818                self.remove(&key);
819            }
820        }
821    }
822
823    /// Retains only the elements specified by the predicate, using batch processing.
824    ///
825    /// This is an optimized version of `retain()` that collects all keys to remove first,
826    /// then removes them in a single batch operation with a single lock acquisition.
827    /// This is more efficient when removing many entries, especially in high-contention scenarios.
828    ///
829    /// # Thread Safety
830    ///
831    /// This method has improved performance characteristics:
832    /// - Only acquires the lock twice (once for collection, once for removal)
833    /// - Performs all removals in a single atomic operation
834    /// - Reduces lock contention compared to standard `retain()`
835    ///
836    /// However, it still has the same consistency characteristics as `retain()`:
837    /// - The snapshot might not reflect concurrent modifications
838    /// - There's a window between collecting entries and removing them where
839    ///   other threads might modify the cache
840    ///
841    /// # Examples
842    ///
843    /// ```
844    /// # use sieve_cache::SyncSieveCache;
845    /// let cache = SyncSieveCache::new(100).unwrap();
846    /// cache.insert("key1".to_string(), 100);
847    /// cache.insert("key2".to_string(), 200);
848    /// cache.insert("key3".to_string(), 300);
849    ///
850    /// // Keep only entries with values greater than 150
851    /// cache.retain_batch(|_, value| *value > 150);
852    ///
853    /// assert_eq!(cache.len(), 2);
854    /// assert!(!cache.contains_key(&"key1".to_string()));
855    /// assert!(cache.contains_key(&"key2".to_string()));
856    /// assert!(cache.contains_key(&"key3".to_string()));
857    /// ```
858    pub fn retain_batch<F>(&self, mut f: F)
859    where
860        F: FnMut(&K, &V) -> bool,
861        V: Clone,
862    {
863        // First, safely collect all entries while holding the lock
864        let entries = self.with_lock(|inner_cache| {
865            inner_cache
866                .iter()
867                .map(|(k, v)| (k.clone(), v.clone()))
868                .collect::<Vec<(K, V)>>()
869        });
870
871        // Collect keys to remove without holding the lock
872        let mut keys_to_remove = Vec::new();
873        for (key, value) in entries {
874            if !f(&key, &value) {
875                keys_to_remove.push(key);
876            }
877        }
878
879        // If there are keys to remove, do it in a single batch operation
880        if !keys_to_remove.is_empty() {
881            self.with_lock(|inner_cache| {
882                for key in keys_to_remove {
883                    inner_cache.remove(&key);
884                }
885            });
886        }
887    }
888
889    /// Returns a recommended cache capacity based on current utilization.
890    ///
891    /// This method analyzes the current cache utilization and recommends a new capacity based on:
892    /// - The number of entries with the 'visited' flag set
893    /// - The current capacity and fill ratio
894    /// - A target utilization range
895    ///
896    /// The recommendation aims to keep the cache size optimal:
897    /// - If many entries are frequently accessed (high utilization), it suggests increasing capacity
898    /// - If few entries are accessed frequently (low utilization), it suggests decreasing capacity
899    /// - Within a normal utilization range, it keeps the capacity stable
900    ///
901    /// # Arguments
902    ///
903    /// * `min_factor` - Minimum scaling factor (e.g., 0.5 means never recommend less than 50% of current capacity)
904    /// * `max_factor` - Maximum scaling factor (e.g., 2.0 means never recommend more than 200% of current capacity)
905    /// * `low_threshold` - Utilization threshold below which capacity is reduced (e.g., 0.3 means 30% utilization)
906    /// * `high_threshold` - Utilization threshold above which capacity is increased (e.g., 0.7 means 70% utilization)
907    ///
908    /// # Examples
909    ///
910    /// ```rust
911    /// # use sieve_cache::SyncSieveCache;
912    /// # fn main() {
913    /// # let cache = SyncSieveCache::<String, i32>::new(100).unwrap();
914    /// #
915    /// # // Add items to the cache
916    /// # for i in 0..80 {
917    /// #     cache.insert(i.to_string(), i);
918    /// #     
919    /// #     // Accessing some items to mark them as visited
920    /// #     if i % 2 == 0 {
921    /// #         cache.get(&i.to_string());
922    /// #     }
923    /// # }
924    /// #
925    /// // Get a recommended capacity with default parameters
926    /// let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
927    /// println!("Recommended capacity: {}", recommended);
928    /// # }
929    /// ```
930    ///
931    /// # Default Recommendation Parameters
932    ///
933    /// If you're unsure what parameters to use, these values provide a reasonable starting point:
934    /// - `min_factor`: 0.5 (never recommend less than half current capacity)
935    /// - `max_factor`: 2.0 (never recommend more than twice current capacity)
936    /// - `low_threshold`: 0.3 (reduce capacity if utilization below 30%)
937    /// - `high_threshold`: 0.7 (increase capacity if utilization above 70%)
938    pub fn recommended_capacity(
939        &self,
940        min_factor: f64,
941        max_factor: f64,
942        low_threshold: f64,
943        high_threshold: f64,
944    ) -> usize {
945        self.with_lock(|inner_cache| {
946            inner_cache.recommended_capacity(min_factor, max_factor, low_threshold, high_threshold)
947        })
948    }
949}
950
951#[cfg(test)]
952mod tests {
953    use super::*;
954    use std::thread;
955
956    #[test]
957    fn test_sync_cache() {
958        let cache = SyncSieveCache::new(100).unwrap();
959
960        // Insert a value
961        assert!(cache.insert("key1".to_string(), "value1".to_string()));
962
963        // Read back the value
964        assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
965
966        // Check contains_key
967        assert!(cache.contains_key(&"key1".to_string()));
968
969        // Check capacity and length
970        assert_eq!(cache.capacity(), 100);
971        assert_eq!(cache.len(), 1);
972
973        // Remove a value
974        assert_eq!(
975            cache.remove(&"key1".to_string()),
976            Some("value1".to_string())
977        );
978        assert_eq!(cache.len(), 0);
979        assert!(cache.is_empty());
980    }
981
982    #[test]
983    fn test_multithreaded_access() {
984        let cache = SyncSieveCache::new(100).unwrap();
985        let cache_clone = cache.clone();
986
987        // Add some initial data
988        cache.insert("shared".to_string(), "initial".to_string());
989
990        // Spawn a thread that updates the cache
991        let thread = thread::spawn(move || {
992            cache_clone.insert("shared".to_string(), "updated".to_string());
993            cache_clone.insert("thread_only".to_string(), "thread_value".to_string());
994        });
995
996        // Main thread operations
997        cache.insert("main_only".to_string(), "main_value".to_string());
998
999        // Wait for thread to complete
1000        thread.join().unwrap();
1001
1002        // Verify results
1003        assert_eq!(
1004            cache.get(&"shared".to_string()),
1005            Some("updated".to_string())
1006        );
1007        assert_eq!(
1008            cache.get(&"thread_only".to_string()),
1009            Some("thread_value".to_string())
1010        );
1011        assert_eq!(
1012            cache.get(&"main_only".to_string()),
1013            Some("main_value".to_string())
1014        );
1015        assert_eq!(cache.len(), 3);
1016    }
1017
1018    #[test]
1019    fn test_with_lock() {
1020        let cache = SyncSieveCache::new(100).unwrap();
1021
1022        // Perform multiple operations atomically
1023        cache.with_lock(|inner_cache| {
1024            inner_cache.insert("key1".to_string(), "value1".to_string());
1025            inner_cache.insert("key2".to_string(), "value2".to_string());
1026            inner_cache.insert("key3".to_string(), "value3".to_string());
1027        });
1028
1029        assert_eq!(cache.len(), 3);
1030    }
1031
1032    #[test]
1033    fn test_get_mut() {
1034        let cache = SyncSieveCache::new(100).unwrap();
1035        cache.insert("key".to_string(), "value".to_string());
1036
1037        // Modify the value in-place
1038        let modified = cache.get_mut(&"key".to_string(), |value| {
1039            *value = "new_value".to_string();
1040        });
1041        assert!(modified);
1042
1043        // Verify the value was updated
1044        assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
1045
1046        // Try to modify a non-existent key
1047        let modified = cache.get_mut(&"missing".to_string(), |_| {
1048            panic!("This should not be called");
1049        });
1050        assert!(!modified);
1051    }
1052
1053    #[test]
1054    fn test_clear() {
1055        let cache = SyncSieveCache::new(10).unwrap();
1056        cache.insert("key1".to_string(), "value1".to_string());
1057        cache.insert("key2".to_string(), "value2".to_string());
1058
1059        assert_eq!(cache.len(), 2);
1060        assert!(!cache.is_empty());
1061
1062        cache.clear();
1063
1064        assert_eq!(cache.len(), 0);
1065        assert!(cache.is_empty());
1066        assert_eq!(cache.get(&"key1".to_string()), None);
1067        assert_eq!(cache.get(&"key2".to_string()), None);
1068    }
1069
1070    #[test]
1071    fn test_keys_values_entries() {
1072        let cache = SyncSieveCache::new(10).unwrap();
1073        cache.insert("key1".to_string(), "value1".to_string());
1074        cache.insert("key2".to_string(), "value2".to_string());
1075
1076        // Test keys
1077        let keys = cache.keys();
1078        assert_eq!(keys.len(), 2);
1079        assert!(keys.contains(&"key1".to_string()));
1080        assert!(keys.contains(&"key2".to_string()));
1081
1082        // Test values
1083        let values = cache.values();
1084        assert_eq!(values.len(), 2);
1085        assert!(values.contains(&"value1".to_string()));
1086        assert!(values.contains(&"value2".to_string()));
1087
1088        // Test entries
1089        let entries = cache.entries();
1090        assert_eq!(entries.len(), 2);
1091        assert!(entries.contains(&("key1".to_string(), "value1".to_string())));
1092        assert!(entries.contains(&("key2".to_string(), "value2".to_string())));
1093    }
1094
1095    #[test]
1096    fn test_for_each_methods() {
1097        let cache = SyncSieveCache::new(10).unwrap();
1098        cache.insert("key1".to_string(), "value1".to_string());
1099        cache.insert("key2".to_string(), "value2".to_string());
1100
1101        // Test for_each_value
1102        cache.for_each_value(|value| {
1103            *value = format!("{}_updated", value);
1104        });
1105
1106        assert_eq!(
1107            cache.get(&"key1".to_string()),
1108            Some("value1_updated".to_string())
1109        );
1110        assert_eq!(
1111            cache.get(&"key2".to_string()),
1112            Some("value2_updated".to_string())
1113        );
1114
1115        // Test for_each_entry
1116        cache.for_each_entry(|(key, value)| {
1117            if key == "key1" {
1118                *value = format!("{}_special", value);
1119            }
1120        });
1121
1122        assert_eq!(
1123            cache.get(&"key1".to_string()),
1124            Some("value1_updated_special".to_string())
1125        );
1126        assert_eq!(
1127            cache.get(&"key2".to_string()),
1128            Some("value2_updated".to_string())
1129        );
1130    }
1131
1132    #[test]
1133    fn test_retain() {
1134        let cache = SyncSieveCache::new(10).unwrap();
1135
1136        // Add some entries
1137        cache.insert("even1".to_string(), 2);
1138        cache.insert("even2".to_string(), 4);
1139        cache.insert("odd1".to_string(), 1);
1140        cache.insert("odd2".to_string(), 3);
1141
1142        assert_eq!(cache.len(), 4);
1143
1144        // Keep only entries with even values
1145        cache.retain(|_, v| v % 2 == 0);
1146
1147        assert_eq!(cache.len(), 2);
1148        assert!(cache.contains_key(&"even1".to_string()));
1149        assert!(cache.contains_key(&"even2".to_string()));
1150        assert!(!cache.contains_key(&"odd1".to_string()));
1151        assert!(!cache.contains_key(&"odd2".to_string()));
1152
1153        // Keep only entries with keys containing '1'
1154        cache.retain(|k, _| k.contains('1'));
1155
1156        assert_eq!(cache.len(), 1);
1157        assert!(cache.contains_key(&"even1".to_string()));
1158        assert!(!cache.contains_key(&"even2".to_string()));
1159    }
1160
1161    #[test]
1162    fn test_recommended_capacity() {
1163        // Test case 1: Empty cache - should return current capacity
1164        let cache = SyncSieveCache::<String, u32>::new(100).unwrap();
1165        assert_eq!(cache.recommended_capacity(0.5, 2.0, 0.3, 0.7), 100);
1166
1167        // Test case 2: Low utilization (few visited nodes)
1168        let cache = SyncSieveCache::new(100).unwrap();
1169        // Fill the cache first without marking anything as visited
1170        for i in 0..90 {
1171            cache.insert(i.to_string(), i);
1172        }
1173
1174        // Now mark only a tiny fraction as visited
1175        for i in 0..5 {
1176            cache.get(&i.to_string()); // Only 5% visited
1177        }
1178
1179        // With very low utilization and high fill, should recommend decreasing capacity
1180        let recommended = cache.recommended_capacity(0.5, 2.0, 0.1, 0.7); // Lower threshold to ensure test passes
1181        assert!(recommended < 100);
1182        assert!(recommended >= 50); // Should not go below min_factor
1183
1184        // Test case 3: High utilization (many visited nodes)
1185        let cache = SyncSieveCache::new(100).unwrap();
1186        for i in 0..90 {
1187            cache.insert(i.to_string(), i);
1188            // Mark 80% as visited
1189            if i % 10 != 0 {
1190                cache.get(&i.to_string());
1191            }
1192        }
1193        // With 80% utilization, should recommend increasing capacity
1194        let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
1195        assert!(recommended > 100);
1196        assert!(recommended <= 200); // Should not go above max_factor
1197
1198        // Test case 4: Normal utilization (should keep capacity the same)
1199        let cache = SyncSieveCache::new(100).unwrap();
1200        for i in 0..90 {
1201            cache.insert(i.to_string(), i);
1202            // Mark 50% as visited
1203            if i % 2 == 0 {
1204                cache.get(&i.to_string());
1205            }
1206        }
1207        // With 50% utilization (between thresholds), capacity should be fairly stable
1208        let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
1209        assert!(
1210            recommended >= 95,
1211            "With normal utilization, capacity should be close to original"
1212        );
1213        assert!(
1214            recommended <= 100,
1215            "With normal utilization, capacity should not exceed original"
1216        );
1217
1218        // Test case 5: Low fill ratio (few entries relative to capacity)
1219        let cache = SyncSieveCache::new(2000).unwrap();
1220        // Add only a few entries (5% of capacity)
1221        for i in 0..100 {
1222            cache.insert(i.to_string(), i);
1223            // Mark all as visited to simulate high hit rate
1224            cache.get(&i.to_string());
1225        }
1226
1227        // Even though utilization is high (100% visited), the fill ratio is very low (5%)
1228        // so it should still recommend decreasing capacity
1229        let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
1230        assert!(
1231            recommended < 2000,
1232            "With low fill ratio, capacity should be decreased despite high hit rate"
1233        );
1234        assert!(
1235            recommended >= 1000, // min_factor = 0.5
1236            "Capacity should not go below min_factor of current capacity"
1237        );
1238    }
1239
1240    #[test]
1241    fn test_deadlock_prevention() {
1242        let cache = Arc::new(SyncSieveCache::new(100).unwrap());
1243
1244        // Add some initial data
1245        cache.insert("key1".to_string(), 1);
1246        cache.insert("key2".to_string(), 2);
1247
1248        // Clone for use in threads
1249        let cache_clone1 = cache.clone();
1250        let cache_clone2 = cache.clone();
1251
1252        // Thread 1: Recursively accesses the cache within get_mut callback
1253        let thread1 = thread::spawn(move || {
1254            cache_clone1.get_mut(&"key1".to_string(), |value| {
1255                // This would deadlock with an unsafe implementation!
1256                // Attempt to get another value while holding the lock
1257                let value2 = cache_clone1.get(&"key2".to_string());
1258                assert_eq!(value2, Some(2));
1259
1260                // Even modify another value
1261                cache_clone1.insert("key3".to_string(), 3);
1262
1263                *value += 10;
1264            });
1265        });
1266
1267        // Thread 2: Also performs operations that would deadlock with unsafe impl
1268        let thread2 = thread::spawn(move || {
1269            // Sleep to ensure thread1 starts first
1270            thread::sleep(std::time::Duration::from_millis(10));
1271
1272            // These operations would deadlock if thread1 held a lock during its callback
1273            cache_clone2.insert("key4".to_string(), 4);
1274            assert_eq!(cache_clone2.get(&"key2".to_string()), Some(2));
1275        });
1276
1277        // Both threads should complete without deadlock
1278        thread1.join().unwrap();
1279        thread2.join().unwrap();
1280
1281        // Verify final state
1282        assert_eq!(cache.get(&"key1".to_string()), Some(11)); // 1 + 10
1283        assert_eq!(cache.get(&"key3".to_string()), Some(3));
1284        assert_eq!(cache.get(&"key4".to_string()), Some(4));
1285    }
1286}