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` holds the lock while executing the callback, which may lead to contention
29///     with long-running callbacks
30///   - `for_each_value`, `for_each_entry`, and `retain` collect data under the lock, then
31///     release it before processing to avoid blocking other threads, meaning they operate
32///     on a snapshot and may miss concurrent modifications
33///
34/// ## Consistency Guarantees
35///
36/// - Operations on individual keys are atomic and isolated
37/// - Snapshot-based operations (e.g., iteration, bulk operations) may not reflect
38///   concurrent modifications
39/// - When using callback functions, beware of causing deadlocks by acquiring other locks
40///   or making recursive calls to the same cache
41///
42/// # Examples
43///
44/// ```
45/// # use sieve_cache::SyncSieveCache;
46/// let cache = SyncSieveCache::new(100).unwrap();
47///
48/// // Multiple threads can safely access the cache
49/// cache.insert("key1".to_string(), "value1".to_string());
50/// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
51/// ```
52///
53/// Example with callbacks:
54///
55/// ```
56/// # use sieve_cache::SyncSieveCache;
57/// # use std::thread;
58/// let cache = SyncSieveCache::new(100).unwrap();
59/// cache.insert("key1".to_string(), 1);
60/// cache.insert("key2".to_string(), 2);
61///
62/// // Create a clone to move into another thread
63/// let cache_clone = cache.clone();
64///
65/// // Spawn a thread that modifies values
66/// let handle = thread::spawn(move || {
67///     // The cache is safely accessible from multiple threads
68///     cache_clone.for_each_value(|value| {
69///         *value += 10;  // Add 10 to each value
70///     });
71/// });
72///
73/// // Wait for the background thread to complete
74/// handle.join().unwrap();
75///
76/// // Values have been updated
77/// assert_eq!(cache.get(&"key1".to_string()), Some(11));
78/// assert_eq!(cache.get(&"key2".to_string()), Some(12));
79/// ```
80#[derive(Clone)]
81pub struct SyncSieveCache<K, V>
82where
83    K: Eq + Hash + Clone + Send + Sync,
84    V: Send + Sync,
85{
86    inner: Arc<Mutex<SieveCache<K, V>>>,
87}
88
89unsafe impl<K, V> Sync for SyncSieveCache<K, V>
90where
91    K: Eq + Hash + Clone + Send + Sync,
92    V: Send + Sync,
93{
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 mut 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            if let Some(v) = guard.get_mut(key) {
370                // Clone the value before releasing the lock
371                Some(v.clone())
372            } else {
373                None
374            }
375        };
376
377        if let Some(mut value) = value_opt {
378            // Execute the callback on the cloned value without holding the lock
379            f(&mut value);
380
381            // Update the value back to the cache
382            let mut guard = self.locked_cache();
383            if let Some(original) = guard.get_mut(key) {
384                *original = value;
385                true
386            } else {
387                // Key was removed while callback was executing
388                false
389            }
390        } else {
391            false
392        }
393    }
394
395    /// Maps `key` to `value` in the cache, possibly evicting old entries.
396    ///
397    /// This method returns `true` when this is a new entry, and `false` if an existing entry was
398    /// updated.
399    ///
400    /// # Examples
401    ///
402    /// ```
403    /// # use sieve_cache::SyncSieveCache;
404    /// let cache = SyncSieveCache::new(100).unwrap();
405    ///
406    /// // Insert a new key
407    /// assert!(cache.insert("key1".to_string(), "value1".to_string()));
408    ///
409    /// // Update an existing key
410    /// assert!(!cache.insert("key1".to_string(), "updated_value".to_string()));
411    /// ```
412    pub fn insert(&self, key: K, value: V) -> bool {
413        let mut guard = self.locked_cache();
414        guard.insert(key, value)
415    }
416
417    /// Removes the cache entry mapped to by `key`.
418    ///
419    /// This method returns the value removed from the cache. If `key` did not map to any value,
420    /// then this returns `None`.
421    ///
422    /// # Examples
423    ///
424    /// ```
425    /// # use sieve_cache::SyncSieveCache;
426    /// let cache = SyncSieveCache::new(100).unwrap();
427    /// cache.insert("key".to_string(), "value".to_string());
428    ///
429    /// // Remove an existing key
430    /// assert_eq!(cache.remove(&"key".to_string()), Some("value".to_string()));
431    ///
432    /// // Attempt to remove a missing key
433    /// assert_eq!(cache.remove(&"key".to_string()), None);
434    /// ```
435    pub fn remove<Q>(&self, key: &Q) -> Option<V>
436    where
437        K: Borrow<Q>,
438        Q: Eq + Hash + ?Sized,
439    {
440        let mut guard = self.locked_cache();
441        guard.remove(key)
442    }
443
444    /// Removes and returns a value from the cache that was not recently accessed.
445    ///
446    /// This implements the SIEVE eviction algorithm to select an entry for removal.
447    /// If no suitable value exists, this returns `None`.
448    ///
449    /// # Examples
450    ///
451    /// ```
452    /// # use sieve_cache::SyncSieveCache;
453    /// let cache = SyncSieveCache::new(2).unwrap();
454    /// cache.insert("key1".to_string(), "value1".to_string());
455    /// cache.insert("key2".to_string(), "value2".to_string());
456    ///
457    /// // Accessing key1 marks it as recently used
458    /// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
459    ///
460    /// // Insert a new key, which should evict key2
461    /// cache.insert("key3".to_string(), "value3".to_string());
462    ///
463    /// // key2 should have been evicted
464    /// assert_eq!(cache.get(&"key2".to_string()), None);
465    /// ```
466    pub fn evict(&self) -> Option<V> {
467        let mut guard = self.locked_cache();
468        guard.evict()
469    }
470
471    /// Removes all entries from the cache.
472    ///
473    /// This operation clears all stored values and resets the cache to an empty state,
474    /// while maintaining the original capacity.
475    ///
476    /// # Examples
477    ///
478    /// ```
479    /// # use sieve_cache::SyncSieveCache;
480    /// let cache = SyncSieveCache::new(100).unwrap();
481    /// cache.insert("key1".to_string(), "value1".to_string());
482    /// cache.insert("key2".to_string(), "value2".to_string());
483    ///
484    /// assert_eq!(cache.len(), 2);
485    ///
486    /// cache.clear();
487    /// assert_eq!(cache.len(), 0);
488    /// assert!(cache.is_empty());
489    /// ```
490    pub fn clear(&self) {
491        let mut guard = self.locked_cache();
492        guard.clear();
493    }
494
495    /// Returns an iterator over all keys in the cache.
496    ///
497    /// The order of keys is not specified and should not be relied upon.
498    ///
499    /// # Examples
500    ///
501    /// ```
502    /// # use sieve_cache::SyncSieveCache;
503    /// # use std::collections::HashSet;
504    /// let cache = SyncSieveCache::new(100).unwrap();
505    /// cache.insert("key1".to_string(), "value1".to_string());
506    /// cache.insert("key2".to_string(), "value2".to_string());
507    ///
508    /// let keys: HashSet<_> = cache.keys().into_iter().collect();
509    /// assert_eq!(keys.len(), 2);
510    /// assert!(keys.contains(&"key1".to_string()));
511    /// assert!(keys.contains(&"key2".to_string()));
512    /// ```
513    pub fn keys(&self) -> Vec<K> {
514        let guard = self.locked_cache();
515        guard.keys().cloned().collect()
516    }
517
518    /// Returns all values in the cache.
519    ///
520    /// The order of values is not specified and should not be relied upon.
521    ///
522    /// # Examples
523    ///
524    /// ```
525    /// # use sieve_cache::SyncSieveCache;
526    /// # use std::collections::HashSet;
527    /// let cache = SyncSieveCache::new(100).unwrap();
528    /// cache.insert("key1".to_string(), "value1".to_string());
529    /// cache.insert("key2".to_string(), "value2".to_string());
530    ///
531    /// let values: HashSet<_> = cache.values().into_iter().collect();
532    /// assert_eq!(values.len(), 2);
533    /// assert!(values.contains(&"value1".to_string()));
534    /// assert!(values.contains(&"value2".to_string()));
535    /// ```
536    pub fn values(&self) -> Vec<V>
537    where
538        V: Clone,
539    {
540        let guard = self.locked_cache();
541        guard.values().cloned().collect()
542    }
543
544    /// Returns all key-value pairs in the cache.
545    ///
546    /// The order of pairs is not specified and should not be relied upon.
547    ///
548    /// # Examples
549    ///
550    /// ```
551    /// # use sieve_cache::SyncSieveCache;
552    /// # use std::collections::HashMap;
553    /// let cache = SyncSieveCache::new(100).unwrap();
554    /// cache.insert("key1".to_string(), "value1".to_string());
555    /// cache.insert("key2".to_string(), "value2".to_string());
556    ///
557    /// let entries: HashMap<_, _> = cache.entries().into_iter().collect();
558    /// assert_eq!(entries.len(), 2);
559    /// assert_eq!(entries.get(&"key1".to_string()), Some(&"value1".to_string()));
560    /// assert_eq!(entries.get(&"key2".to_string()), Some(&"value2".to_string()));
561    /// ```
562    pub fn entries(&self) -> Vec<(K, V)>
563    where
564        V: Clone,
565    {
566        let guard = self.locked_cache();
567        guard.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
568    }
569
570    /// Applies a function to all values in the cache.
571    ///
572    /// This method safely processes values by collecting them with the lock held,
573    /// then releasing the lock before applying the function to each value individually.
574    /// If the function modifies the values, the changes are saved back to the cache.
575    ///
576    /// # Thread Safety
577    ///
578    /// This method operates in three phases:
579    /// 1. It acquires the lock and creates a complete snapshot of the cache
580    /// 2. It releases the lock and applies the callback to each value
581    /// 3. It acquires the lock again individually for each value when writing changes back
582    ///
583    /// This approach means:
584    /// - The lock is not held during callback execution, preventing lock contention
585    /// - If other threads modify the cache between steps 1 and 3, those changes might be overwritten
586    /// - The callback sees a point-in-time snapshot that might not reflect the latest state
587    /// - For long-running operations, consider using individual key operations instead
588    ///
589    /// # Examples
590    ///
591    /// ```
592    /// # use sieve_cache::SyncSieveCache;
593    /// let cache = SyncSieveCache::new(100).unwrap();
594    /// cache.insert("key1".to_string(), "value1".to_string());
595    /// cache.insert("key2".to_string(), "value2".to_string());
596    ///
597    /// // Update all values by appending text
598    /// cache.for_each_value(|value| {
599    ///     *value = format!("{}_updated", value);
600    /// });
601    ///
602    /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_updated".to_string()));
603    /// assert_eq!(cache.get(&"key2".to_string()), Some("value2_updated".to_string()));
604    /// ```
605    pub fn for_each_value<F>(&self, mut f: F)
606    where
607        F: FnMut(&mut V),
608        V: Clone,
609    {
610        // First, safely collect all keys and values while holding the lock
611        let entries = self.with_lock(|inner_cache| {
612            inner_cache
613                .iter()
614                .map(|(k, v)| (k.clone(), v.clone()))
615                .collect::<Vec<(K, V)>>()
616        });
617
618        // Process each value outside the lock
619        let mut updated_entries = Vec::new();
620        for (key, mut value) in entries {
621            f(&mut value);
622            updated_entries.push((key, value));
623        }
624
625        // Update any changed values back to the cache
626        for (key, value) in updated_entries {
627            self.insert(key, value);
628        }
629    }
630
631    /// Applies a function to all key-value pairs in the cache.
632    ///
633    /// This method safely processes key-value pairs by collecting them with the lock held,
634    /// then releasing the lock before applying the function to each pair individually.
635    /// If the function modifies the values, the changes are saved back to the cache.
636    ///
637    /// # Thread Safety
638    ///
639    /// This method operates in three phases:
640    /// 1. It acquires the lock and creates a complete snapshot of the cache
641    /// 2. It releases the lock and applies the callback to each key-value pair
642    /// 3. It acquires the lock again individually for each entry when writing changes back
643    ///
644    /// This approach means:
645    /// - The lock is not held during callback execution, preventing lock contention
646    /// - If other threads modify the cache between steps 1 and 3, those changes might be overwritten
647    /// - The callback sees a point-in-time snapshot that might not reflect the latest state
648    /// - For long-running operations, consider using individual key operations instead
649    ///
650    /// # Examples
651    ///
652    /// ```
653    /// # use sieve_cache::SyncSieveCache;
654    /// let cache = SyncSieveCache::new(100).unwrap();
655    /// cache.insert("key1".to_string(), "value1".to_string());
656    /// cache.insert("key2".to_string(), "value2".to_string());
657    ///
658    /// // Update all values associated with keys containing '1'
659    /// cache.for_each_entry(|(key, value)| {
660    ///     if key.contains('1') {
661    ///         *value = format!("{}_special", value);
662    ///     }
663    /// });
664    ///
665    /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_special".to_string()));
666    /// assert_eq!(cache.get(&"key2".to_string()), Some("value2".to_string()));
667    /// ```
668    pub fn for_each_entry<F>(&self, mut f: F)
669    where
670        F: FnMut((&K, &mut V)),
671        V: Clone,
672    {
673        // First, safely collect all keys and values while holding the lock
674        let entries = self.with_lock(|inner_cache| {
675            inner_cache
676                .iter()
677                .map(|(k, v)| (k.clone(), v.clone()))
678                .collect::<Vec<(K, V)>>()
679        });
680
681        // Process each entry outside the lock
682        let mut updated_entries = Vec::new();
683        for (key, mut value) in entries {
684            let key_ref = &key;
685            f((key_ref, &mut value));
686            updated_entries.push((key, value));
687        }
688
689        // Update any changed values back to the cache
690        for (key, value) in updated_entries {
691            self.insert(key, value);
692        }
693    }
694
695    /// Gets exclusive access to the underlying cache to perform multiple operations atomically.
696    ///
697    /// This is useful when you need to perform a series of operations that depend on each other
698    /// and you want to ensure that no other thread can access the cache between operations.
699    ///
700    /// # Thread Safety
701    ///
702    /// This method provides the strongest thread safety guarantees by:
703    ///
704    /// - Acquiring the lock once for the entire duration of the callback
705    /// - Allowing multiple operations to be performed atomically within a single lock scope
706    /// - Ensuring all operations within the callback are fully isolated from other threads
707    ///
708    /// However, this also means:
709    ///
710    /// - The entire cache is locked during the callback execution
711    /// - Other threads will be blocked from accessing the cache until the callback completes
712    /// - Long-running operations within the callback will cause high contention
713    /// - Callbacks must never try to access the same cache instance recursively (deadlock risk)
714    ///
715    /// This method should be used when you need atomic multi-step operations but used
716    /// sparingly in high-concurrency environments.
717    ///
718    /// # Examples
719    ///
720    /// ```
721    /// # use sieve_cache::SyncSieveCache;
722    /// let cache = SyncSieveCache::new(100).unwrap();
723    ///
724    /// cache.with_lock(|inner_cache| {
725    ///     // Perform multiple operations atomically
726    ///     inner_cache.insert("key1".to_string(), "value1".to_string());
727    ///     inner_cache.insert("key2".to_string(), "value2".to_string());
728    ///
729    ///     // We can check internal state mid-transaction
730    ///     assert_eq!(inner_cache.len(), 2);
731    /// });
732    /// ```
733    pub fn with_lock<F, T>(&self, f: F) -> T
734    where
735        F: FnOnce(&mut SieveCache<K, V>) -> T,
736    {
737        let mut guard = self.locked_cache();
738        f(&mut guard)
739    }
740
741    /// Retains only the elements specified by the predicate.
742    ///
743    /// Removes all entries for which the provided function returns `false`.
744    /// The elements are visited in arbitrary, unspecified order.
745    ///
746    /// This method offers two modes of operation:
747    /// - Default mode: evaluates predicates outside the lock, with separate remove operations
748    /// - Batch mode: evaluates predicates outside the lock, then performs removals in a single batch
749    ///
750    /// # Thread Safety
751    ///
752    /// This method operates in three phases:
753    /// 1. It acquires the lock and creates a complete snapshot of the cache
754    /// 2. It releases the lock and applies the predicate to each entry
755    /// 3. It either:
756    ///    - Acquires the lock again individually for each entry to be removed (default), or
757    ///    - Acquires the lock once and removes all entries in a batch (batch mode)
758    ///
759    /// This approach means:
760    /// - The lock is not held during predicate execution, preventing lock contention
761    /// - If other threads modify the cache between steps 1 and 3, the method may:
762    ///   - Remove entries that were modified and would now pass the predicate
763    ///   - Miss removing entries added after the snapshot was taken
764    /// - The predicate sees a point-in-time snapshot that might not reflect the latest state
765    /// - For strict consistency requirements, use `with_lock` instead
766    ///
767    /// # Examples
768    ///
769    /// Basic usage:
770    ///
771    /// ```
772    /// # use sieve_cache::SyncSieveCache;
773    /// let cache = SyncSieveCache::new(100).unwrap();
774    /// cache.insert("key1".to_string(), 100);
775    /// cache.insert("key2".to_string(), 200);
776    /// cache.insert("key3".to_string(), 300);
777    ///
778    /// // Keep only entries with values greater than 150
779    /// cache.retain(|_, value| *value > 150);
780    ///
781    /// assert_eq!(cache.len(), 2);
782    /// assert!(!cache.contains_key(&"key1".to_string()));
783    /// assert!(cache.contains_key(&"key2".to_string()));
784    /// assert!(cache.contains_key(&"key3".to_string()));
785    /// ```
786    ///
787    /// Using batch mode (more efficient):
788    ///
789    /// ```
790    /// # use sieve_cache::SyncSieveCache;
791    /// let cache = SyncSieveCache::new(100).unwrap();
792    /// cache.insert("key1".to_string(), 100);
793    /// cache.insert("key2".to_string(), 200);
794    /// cache.insert("key3".to_string(), 300);
795    ///
796    /// // Keep only entries with values greater than 150 (batch mode)
797    /// cache.retain_batch(|_, value| *value > 150);
798    ///
799    /// assert_eq!(cache.len(), 2);
800    /// assert!(!cache.contains_key(&"key1".to_string()));
801    /// assert!(cache.contains_key(&"key2".to_string()));
802    /// assert!(cache.contains_key(&"key3".to_string()));
803    /// ```
804    pub fn retain<F>(&self, mut f: F)
805    where
806        F: FnMut(&K, &V) -> bool,
807        V: Clone,
808    {
809        // First, safely collect all entries while holding the lock
810        let entries = self.with_lock(|inner_cache| {
811            inner_cache
812                .iter()
813                .map(|(k, v)| (k.clone(), v.clone()))
814                .collect::<Vec<(K, V)>>()
815        });
816
817        // Now check each entry against the predicate without holding the lock
818        for (key, value) in entries {
819            if !f(&key, &value) {
820                // Remove entries that don't match the predicate
821                // This acquires the lock for each removal operation
822                self.remove(&key);
823            }
824        }
825    }
826
827    /// Retains only the elements specified by the predicate, using batch processing.
828    ///
829    /// This is an optimized version of `retain()` that collects all keys to remove first,
830    /// then removes them in a single batch operation with a single lock acquisition.
831    /// This is more efficient when removing many entries, especially in high-contention scenarios.
832    ///
833    /// # Thread Safety
834    ///
835    /// This method has improved performance characteristics:
836    /// - Only acquires the lock twice (once for collection, once for removal)
837    /// - Performs all removals in a single atomic operation
838    /// - Reduces lock contention compared to standard `retain()`
839    ///
840    /// However, it still has the same consistency characteristics as `retain()`:
841    /// - The snapshot might not reflect concurrent modifications
842    /// - There's a window between collecting entries and removing them where
843    ///   other threads might modify the cache
844    ///
845    /// # Examples
846    ///
847    /// ```
848    /// # use sieve_cache::SyncSieveCache;
849    /// let cache = SyncSieveCache::new(100).unwrap();
850    /// cache.insert("key1".to_string(), 100);
851    /// cache.insert("key2".to_string(), 200);
852    /// cache.insert("key3".to_string(), 300);
853    ///
854    /// // Keep only entries with values greater than 150
855    /// cache.retain_batch(|_, value| *value > 150);
856    ///
857    /// assert_eq!(cache.len(), 2);
858    /// assert!(!cache.contains_key(&"key1".to_string()));
859    /// assert!(cache.contains_key(&"key2".to_string()));
860    /// assert!(cache.contains_key(&"key3".to_string()));
861    /// ```
862    pub fn retain_batch<F>(&self, mut f: F)
863    where
864        F: FnMut(&K, &V) -> bool,
865        V: Clone,
866    {
867        // First, safely collect all entries while holding the lock
868        let entries = self.with_lock(|inner_cache| {
869            inner_cache
870                .iter()
871                .map(|(k, v)| (k.clone(), v.clone()))
872                .collect::<Vec<(K, V)>>()
873        });
874
875        // Collect keys to remove without holding the lock
876        let mut keys_to_remove = Vec::new();
877        for (key, value) in entries {
878            if !f(&key, &value) {
879                keys_to_remove.push(key);
880            }
881        }
882
883        // If there are keys to remove, do it in a single batch operation
884        if !keys_to_remove.is_empty() {
885            self.with_lock(|inner_cache| {
886                for key in keys_to_remove {
887                    inner_cache.remove(&key);
888                }
889            });
890        }
891    }
892}
893
894#[cfg(test)]
895mod tests {
896    use super::*;
897    use std::thread;
898
899    #[test]
900    fn test_sync_cache() {
901        let cache = SyncSieveCache::new(100).unwrap();
902
903        // Insert a value
904        assert!(cache.insert("key1".to_string(), "value1".to_string()));
905
906        // Read back the value
907        assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
908
909        // Check contains_key
910        assert!(cache.contains_key(&"key1".to_string()));
911
912        // Check capacity and length
913        assert_eq!(cache.capacity(), 100);
914        assert_eq!(cache.len(), 1);
915
916        // Remove a value
917        assert_eq!(
918            cache.remove(&"key1".to_string()),
919            Some("value1".to_string())
920        );
921        assert_eq!(cache.len(), 0);
922        assert!(cache.is_empty());
923    }
924
925    #[test]
926    fn test_multithreaded_access() {
927        let cache = SyncSieveCache::new(100).unwrap();
928        let cache_clone = cache.clone();
929
930        // Add some initial data
931        cache.insert("shared".to_string(), "initial".to_string());
932
933        // Spawn a thread that updates the cache
934        let thread = thread::spawn(move || {
935            cache_clone.insert("shared".to_string(), "updated".to_string());
936            cache_clone.insert("thread_only".to_string(), "thread_value".to_string());
937        });
938
939        // Main thread operations
940        cache.insert("main_only".to_string(), "main_value".to_string());
941
942        // Wait for thread to complete
943        thread.join().unwrap();
944
945        // Verify results
946        assert_eq!(
947            cache.get(&"shared".to_string()),
948            Some("updated".to_string())
949        );
950        assert_eq!(
951            cache.get(&"thread_only".to_string()),
952            Some("thread_value".to_string())
953        );
954        assert_eq!(
955            cache.get(&"main_only".to_string()),
956            Some("main_value".to_string())
957        );
958        assert_eq!(cache.len(), 3);
959    }
960
961    #[test]
962    fn test_with_lock() {
963        let cache = SyncSieveCache::new(100).unwrap();
964
965        // Perform multiple operations atomically
966        cache.with_lock(|inner_cache| {
967            inner_cache.insert("key1".to_string(), "value1".to_string());
968            inner_cache.insert("key2".to_string(), "value2".to_string());
969            inner_cache.insert("key3".to_string(), "value3".to_string());
970        });
971
972        assert_eq!(cache.len(), 3);
973    }
974
975    #[test]
976    fn test_get_mut() {
977        let cache = SyncSieveCache::new(100).unwrap();
978        cache.insert("key".to_string(), "value".to_string());
979
980        // Modify the value in-place
981        let modified = cache.get_mut(&"key".to_string(), |value| {
982            *value = "new_value".to_string();
983        });
984        assert!(modified);
985
986        // Verify the value was updated
987        assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
988
989        // Try to modify a non-existent key
990        let modified = cache.get_mut(&"missing".to_string(), |_| {
991            panic!("This should not be called");
992        });
993        assert!(!modified);
994    }
995
996    #[test]
997    fn test_clear() {
998        let cache = SyncSieveCache::new(10).unwrap();
999        cache.insert("key1".to_string(), "value1".to_string());
1000        cache.insert("key2".to_string(), "value2".to_string());
1001
1002        assert_eq!(cache.len(), 2);
1003        assert!(!cache.is_empty());
1004
1005        cache.clear();
1006
1007        assert_eq!(cache.len(), 0);
1008        assert!(cache.is_empty());
1009        assert_eq!(cache.get(&"key1".to_string()), None);
1010        assert_eq!(cache.get(&"key2".to_string()), None);
1011    }
1012
1013    #[test]
1014    fn test_keys_values_entries() {
1015        let cache = SyncSieveCache::new(10).unwrap();
1016        cache.insert("key1".to_string(), "value1".to_string());
1017        cache.insert("key2".to_string(), "value2".to_string());
1018
1019        // Test keys
1020        let keys = cache.keys();
1021        assert_eq!(keys.len(), 2);
1022        assert!(keys.contains(&"key1".to_string()));
1023        assert!(keys.contains(&"key2".to_string()));
1024
1025        // Test values
1026        let values = cache.values();
1027        assert_eq!(values.len(), 2);
1028        assert!(values.contains(&"value1".to_string()));
1029        assert!(values.contains(&"value2".to_string()));
1030
1031        // Test entries
1032        let entries = cache.entries();
1033        assert_eq!(entries.len(), 2);
1034        assert!(entries.contains(&("key1".to_string(), "value1".to_string())));
1035        assert!(entries.contains(&("key2".to_string(), "value2".to_string())));
1036    }
1037
1038    #[test]
1039    fn test_for_each_methods() {
1040        let cache = SyncSieveCache::new(10).unwrap();
1041        cache.insert("key1".to_string(), "value1".to_string());
1042        cache.insert("key2".to_string(), "value2".to_string());
1043
1044        // Test for_each_value
1045        cache.for_each_value(|value| {
1046            *value = format!("{}_updated", value);
1047        });
1048
1049        assert_eq!(
1050            cache.get(&"key1".to_string()),
1051            Some("value1_updated".to_string())
1052        );
1053        assert_eq!(
1054            cache.get(&"key2".to_string()),
1055            Some("value2_updated".to_string())
1056        );
1057
1058        // Test for_each_entry
1059        cache.for_each_entry(|(key, value)| {
1060            if key == "key1" {
1061                *value = format!("{}_special", value);
1062            }
1063        });
1064
1065        assert_eq!(
1066            cache.get(&"key1".to_string()),
1067            Some("value1_updated_special".to_string())
1068        );
1069        assert_eq!(
1070            cache.get(&"key2".to_string()),
1071            Some("value2_updated".to_string())
1072        );
1073    }
1074
1075    #[test]
1076    fn test_retain() {
1077        let cache = SyncSieveCache::new(10).unwrap();
1078
1079        // Add some entries
1080        cache.insert("even1".to_string(), 2);
1081        cache.insert("even2".to_string(), 4);
1082        cache.insert("odd1".to_string(), 1);
1083        cache.insert("odd2".to_string(), 3);
1084
1085        assert_eq!(cache.len(), 4);
1086
1087        // Keep only entries with even values
1088        cache.retain(|_, v| v % 2 == 0);
1089
1090        assert_eq!(cache.len(), 2);
1091        assert!(cache.contains_key(&"even1".to_string()));
1092        assert!(cache.contains_key(&"even2".to_string()));
1093        assert!(!cache.contains_key(&"odd1".to_string()));
1094        assert!(!cache.contains_key(&"odd2".to_string()));
1095
1096        // Keep only entries with keys containing '1'
1097        cache.retain(|k, _| k.contains('1'));
1098
1099        assert_eq!(cache.len(), 1);
1100        assert!(cache.contains_key(&"even1".to_string()));
1101        assert!(!cache.contains_key(&"even2".to_string()));
1102    }
1103}