use crate::SieveCache;
use std::borrow::Borrow;
use std::fmt;
use std::hash::Hash;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
#[derive(Clone)]
pub struct SyncSieveCache<K, V>
where
K: Eq + Hash + Clone + Send + Sync,
V: Send + Sync,
{
inner: Arc<Mutex<SieveCache<K, V>>>,
}
impl<K, V> Default for SyncSieveCache<K, V>
where
K: Eq + Hash + Clone + Send + Sync,
V: Send + Sync,
{
fn default() -> Self {
Self::new(100).expect("Failed to create cache with default capacity")
}
}
impl<K, V> fmt::Debug for SyncSieveCache<K, V>
where
K: Eq + Hash + Clone + Send + Sync + fmt::Debug,
V: Send + Sync + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let guard = self.locked_cache();
f.debug_struct("SyncSieveCache")
.field("capacity", &guard.capacity())
.field("len", &guard.len())
.finish()
}
}
impl<K, V> From<SieveCache<K, V>> for SyncSieveCache<K, V>
where
K: Eq + Hash + Clone + Send + Sync,
V: Send + Sync,
{
fn from(cache: SieveCache<K, V>) -> Self {
Self {
inner: Arc::new(Mutex::new(cache)),
}
}
}
impl<K, V> IntoIterator for SyncSieveCache<K, V>
where
K: Eq + Hash + Clone + Send + Sync,
V: Clone + Send + Sync,
{
type Item = (K, V);
type IntoIter = std::vec::IntoIter<(K, V)>;
fn into_iter(self) -> Self::IntoIter {
self.entries().into_iter()
}
}
impl<K, V> SyncSieveCache<K, V>
where
K: Eq + Hash + Clone + Send + Sync,
V: Send + Sync,
{
pub fn new(capacity: usize) -> Result<Self, &'static str> {
let cache = SieveCache::new(capacity)?;
Ok(Self {
inner: Arc::new(Mutex::new(cache)),
})
}
#[inline]
fn locked_cache(&self) -> MutexGuard<'_, SieveCache<K, V>> {
self.inner.lock().unwrap_or_else(PoisonError::into_inner)
}
pub fn capacity(&self) -> usize {
self.locked_cache().capacity()
}
pub fn len(&self) -> usize {
self.locked_cache().len()
}
pub fn is_empty(&self) -> bool {
self.locked_cache().is_empty()
}
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
Q: Hash + Eq + ?Sized,
K: Borrow<Q>,
{
let guard = self.locked_cache();
guard.contains_key(key)
}
pub fn get<Q>(&self, key: &Q) -> Option<V>
where
Q: Hash + Eq + ?Sized,
K: Borrow<Q>,
V: Clone,
{
let mut guard = self.locked_cache();
guard.get(key).cloned()
}
pub fn get_mut<Q, F>(&self, key: &Q, f: F) -> bool
where
Q: Hash + Eq + ?Sized,
K: Borrow<Q>,
F: FnOnce(&mut V),
V: Clone,
{
let value_opt = {
let mut guard = self.locked_cache();
guard.get_mut(key).map(|v| v.clone())
};
if let Some(mut value) = value_opt {
f(&mut value);
let mut guard = self.locked_cache();
if let Some(original) = guard.get_mut(key) {
*original = value;
true
} else {
false
}
} else {
false
}
}
pub fn insert(&self, key: K, value: V) -> bool {
let mut guard = self.locked_cache();
guard.insert(key, value)
}
pub fn remove<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
let mut guard = self.locked_cache();
guard.remove(key)
}
pub fn evict(&self) -> Option<V> {
let mut guard = self.locked_cache();
guard.evict()
}
pub fn clear(&self) {
let mut guard = self.locked_cache();
guard.clear();
}
pub fn keys(&self) -> Vec<K> {
let guard = self.locked_cache();
guard.keys().cloned().collect()
}
pub fn values(&self) -> Vec<V>
where
V: Clone,
{
let guard = self.locked_cache();
guard.values().cloned().collect()
}
pub fn entries(&self) -> Vec<(K, V)>
where
V: Clone,
{
let guard = self.locked_cache();
guard.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
}
pub fn for_each_value<F>(&self, mut f: F)
where
F: FnMut(&mut V),
V: Clone,
{
let entries = self.with_lock(|inner_cache| {
inner_cache
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Vec<(K, V)>>()
});
let mut updated_entries = Vec::new();
for (key, mut value) in entries {
f(&mut value);
updated_entries.push((key, value));
}
for (key, value) in updated_entries {
self.insert(key, value);
}
}
pub fn for_each_entry<F>(&self, mut f: F)
where
F: FnMut((&K, &mut V)),
V: Clone,
{
let entries = self.with_lock(|inner_cache| {
inner_cache
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Vec<(K, V)>>()
});
let mut updated_entries = Vec::new();
for (key, mut value) in entries {
let key_ref = &key;
f((key_ref, &mut value));
updated_entries.push((key, value));
}
for (key, value) in updated_entries {
self.insert(key, value);
}
}
pub fn with_lock<F, T>(&self, f: F) -> T
where
F: FnOnce(&mut SieveCache<K, V>) -> T,
{
let mut guard = self.locked_cache();
f(&mut guard)
}
pub fn retain<F>(&self, mut f: F)
where
F: FnMut(&K, &V) -> bool,
V: Clone,
{
let entries = self.with_lock(|inner_cache| {
inner_cache
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Vec<(K, V)>>()
});
for (key, value) in entries {
if !f(&key, &value) {
self.remove(&key);
}
}
}
pub fn retain_batch<F>(&self, mut f: F)
where
F: FnMut(&K, &V) -> bool,
V: Clone,
{
let entries = self.with_lock(|inner_cache| {
inner_cache
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Vec<(K, V)>>()
});
let mut keys_to_remove = Vec::new();
for (key, value) in entries {
if !f(&key, &value) {
keys_to_remove.push(key);
}
}
if !keys_to_remove.is_empty() {
self.with_lock(|inner_cache| {
for key in keys_to_remove {
inner_cache.remove(&key);
}
});
}
}
pub fn recommended_capacity(
&self,
min_factor: f64,
max_factor: f64,
low_threshold: f64,
high_threshold: f64,
) -> usize {
self.with_lock(|inner_cache| {
inner_cache.recommended_capacity(min_factor, max_factor, low_threshold, high_threshold)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_sync_cache() {
let cache = SyncSieveCache::new(100).unwrap();
assert!(cache.insert("key1".to_string(), "value1".to_string()));
assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
assert!(cache.contains_key(&"key1".to_string()));
assert_eq!(cache.capacity(), 100);
assert_eq!(cache.len(), 1);
assert_eq!(
cache.remove(&"key1".to_string()),
Some("value1".to_string())
);
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_multithreaded_access() {
let cache = SyncSieveCache::new(100).unwrap();
let cache_clone = cache.clone();
cache.insert("shared".to_string(), "initial".to_string());
let thread = thread::spawn(move || {
cache_clone.insert("shared".to_string(), "updated".to_string());
cache_clone.insert("thread_only".to_string(), "thread_value".to_string());
});
cache.insert("main_only".to_string(), "main_value".to_string());
thread.join().unwrap();
assert_eq!(
cache.get(&"shared".to_string()),
Some("updated".to_string())
);
assert_eq!(
cache.get(&"thread_only".to_string()),
Some("thread_value".to_string())
);
assert_eq!(
cache.get(&"main_only".to_string()),
Some("main_value".to_string())
);
assert_eq!(cache.len(), 3);
}
#[test]
fn test_with_lock() {
let cache = SyncSieveCache::new(100).unwrap();
cache.with_lock(|inner_cache| {
inner_cache.insert("key1".to_string(), "value1".to_string());
inner_cache.insert("key2".to_string(), "value2".to_string());
inner_cache.insert("key3".to_string(), "value3".to_string());
});
assert_eq!(cache.len(), 3);
}
#[test]
fn test_get_mut() {
let cache = SyncSieveCache::new(100).unwrap();
cache.insert("key".to_string(), "value".to_string());
let modified = cache.get_mut(&"key".to_string(), |value| {
*value = "new_value".to_string();
});
assert!(modified);
assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
let modified = cache.get_mut(&"missing".to_string(), |_| {
panic!("This should not be called");
});
assert!(!modified);
}
#[test]
fn test_clear() {
let cache = SyncSieveCache::new(10).unwrap();
cache.insert("key1".to_string(), "value1".to_string());
cache.insert("key2".to_string(), "value2".to_string());
assert_eq!(cache.len(), 2);
assert!(!cache.is_empty());
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert_eq!(cache.get(&"key1".to_string()), None);
assert_eq!(cache.get(&"key2".to_string()), None);
}
#[test]
fn test_keys_values_entries() {
let cache = SyncSieveCache::new(10).unwrap();
cache.insert("key1".to_string(), "value1".to_string());
cache.insert("key2".to_string(), "value2".to_string());
let keys = cache.keys();
assert_eq!(keys.len(), 2);
assert!(keys.contains(&"key1".to_string()));
assert!(keys.contains(&"key2".to_string()));
let values = cache.values();
assert_eq!(values.len(), 2);
assert!(values.contains(&"value1".to_string()));
assert!(values.contains(&"value2".to_string()));
let entries = cache.entries();
assert_eq!(entries.len(), 2);
assert!(entries.contains(&("key1".to_string(), "value1".to_string())));
assert!(entries.contains(&("key2".to_string(), "value2".to_string())));
}
#[test]
fn test_for_each_methods() {
let cache = SyncSieveCache::new(10).unwrap();
cache.insert("key1".to_string(), "value1".to_string());
cache.insert("key2".to_string(), "value2".to_string());
cache.for_each_value(|value| {
*value = format!("{}_updated", value);
});
assert_eq!(
cache.get(&"key1".to_string()),
Some("value1_updated".to_string())
);
assert_eq!(
cache.get(&"key2".to_string()),
Some("value2_updated".to_string())
);
cache.for_each_entry(|(key, value)| {
if key == "key1" {
*value = format!("{}_special", value);
}
});
assert_eq!(
cache.get(&"key1".to_string()),
Some("value1_updated_special".to_string())
);
assert_eq!(
cache.get(&"key2".to_string()),
Some("value2_updated".to_string())
);
}
#[test]
fn test_retain() {
let cache = SyncSieveCache::new(10).unwrap();
cache.insert("even1".to_string(), 2);
cache.insert("even2".to_string(), 4);
cache.insert("odd1".to_string(), 1);
cache.insert("odd2".to_string(), 3);
assert_eq!(cache.len(), 4);
cache.retain(|_, v| v % 2 == 0);
assert_eq!(cache.len(), 2);
assert!(cache.contains_key(&"even1".to_string()));
assert!(cache.contains_key(&"even2".to_string()));
assert!(!cache.contains_key(&"odd1".to_string()));
assert!(!cache.contains_key(&"odd2".to_string()));
cache.retain(|k, _| k.contains('1'));
assert_eq!(cache.len(), 1);
assert!(cache.contains_key(&"even1".to_string()));
assert!(!cache.contains_key(&"even2".to_string()));
}
#[test]
fn test_recommended_capacity() {
let cache = SyncSieveCache::<String, u32>::new(100).unwrap();
assert_eq!(cache.recommended_capacity(0.5, 2.0, 0.3, 0.7), 100);
let cache = SyncSieveCache::new(100).unwrap();
for i in 0..90 {
cache.insert(i.to_string(), i);
}
for i in 0..5 {
cache.get(&i.to_string()); }
let recommended = cache.recommended_capacity(0.5, 2.0, 0.1, 0.7); assert!(recommended < 100);
assert!(recommended >= 50);
let cache = SyncSieveCache::new(100).unwrap();
for i in 0..90 {
cache.insert(i.to_string(), i);
if i % 10 != 0 {
cache.get(&i.to_string());
}
}
let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
assert!(recommended > 100);
assert!(recommended <= 200);
let cache = SyncSieveCache::new(100).unwrap();
for i in 0..90 {
cache.insert(i.to_string(), i);
if i % 2 == 0 {
cache.get(&i.to_string());
}
}
let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
assert!(
recommended >= 95,
"With normal utilization, capacity should be close to original"
);
assert!(
recommended <= 100,
"With normal utilization, capacity should not exceed original"
);
let cache = SyncSieveCache::new(2000).unwrap();
for i in 0..100 {
cache.insert(i.to_string(), i);
cache.get(&i.to_string());
}
let recommended = cache.recommended_capacity(0.5, 2.0, 0.3, 0.7);
assert!(
recommended < 2000,
"With low fill ratio, capacity should be decreased despite high hit rate"
);
assert!(
recommended >= 1000, "Capacity should not go below min_factor of current capacity"
);
}
#[test]
fn test_deadlock_prevention() {
let cache = Arc::new(SyncSieveCache::new(100).unwrap());
cache.insert("key1".to_string(), 1);
cache.insert("key2".to_string(), 2);
let cache_clone1 = cache.clone();
let cache_clone2 = cache.clone();
let thread1 = thread::spawn(move || {
cache_clone1.get_mut(&"key1".to_string(), |value| {
let value2 = cache_clone1.get(&"key2".to_string());
assert_eq!(value2, Some(2));
cache_clone1.insert("key3".to_string(), 3);
*value += 10;
});
});
let thread2 = thread::spawn(move || {
thread::sleep(std::time::Duration::from_millis(10));
cache_clone2.insert("key4".to_string(), 4);
assert_eq!(cache_clone2.get(&"key2".to_string()), Some(2));
});
thread1.join().unwrap();
thread2.join().unwrap();
assert_eq!(cache.get(&"key1".to_string()), Some(11)); assert_eq!(cache.get(&"key3".to_string()), Some(3));
assert_eq!(cache.get(&"key4".to_string()), Some(4));
}
}