Skip to content

Commit da43443

Browse files
committed
Add get_mut() to the thread-safe variants
1 parent 5f07d97 commit da43443

File tree

4 files changed

+159
-3
lines changed

4 files changed

+159
-3
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sieve-cache"
3-
version = "1.0.0"
3+
version = "1.0.1"
44
edition = "2021"
55
description = "SIEVE cache replacement policy with thread-safe wrappers"
66
homepage = "https://github.com/jedisct1/rust-sieve-cache"
@@ -44,4 +44,4 @@ strip = true
4444
[profile.bench]
4545
opt-level = 3
4646
debug = true
47-
lto = "thin"
47+
lto = "thin"

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,4 +628,4 @@ fn test_visited_flag_update() {
628628
// new entry is added.
629629
cache.insert("key3".to_string(), "value3".to_string());
630630
assert_eq!(cache.get("key1"), Some(&"updated".to_string()));
631-
}
631+
}

src/sharded.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,44 @@ where
304304
guard.get(key).cloned()
305305
}
306306

307+
/// Gets a mutable reference to the value in the cache mapped to by `key` via a callback function.
308+
///
309+
/// If no value exists for `key`, the callback will not be invoked and this returns `false`.
310+
/// Otherwise, the callback is invoked with a mutable reference to the value and this returns `true`.
311+
/// This operation only locks the specific shard containing the key.
312+
///
313+
/// This operation marks the entry as "visited" in the SIEVE algorithm,
314+
/// which affects eviction decisions.
315+
///
316+
/// # Examples
317+
///
318+
/// ```
319+
/// # use sieve_cache::ShardedSieveCache;
320+
/// let cache: ShardedSieveCache<String, String> = ShardedSieveCache::new(100).unwrap();
321+
/// cache.insert("key".to_string(), "value".to_string());
322+
///
323+
/// // Modify the value in-place
324+
/// cache.get_mut(&"key".to_string(), |value| {
325+
/// *value = "new_value".to_string();
326+
/// });
327+
///
328+
/// assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
329+
/// ```
330+
pub fn get_mut<Q, F>(&self, key: &Q, f: F) -> bool
331+
where
332+
Q: Hash + Eq + ?Sized,
333+
K: Borrow<Q>,
334+
F: FnOnce(&mut V),
335+
{
336+
let mut guard = self.locked_shard(key);
337+
if let Some(value) = guard.get_mut(key) {
338+
f(value);
339+
true
340+
} else {
341+
false
342+
}
343+
}
344+
307345
/// Maps `key` to `value` in the cache, possibly evicting old entries from the appropriate shard.
308346
///
309347
/// This method returns `true` when this is a new entry, and `false` if an existing entry was
@@ -611,4 +649,64 @@ mod tests {
611649
assert!(cache.contains_key(&key));
612650
}
613651
}
652+
653+
#[test]
654+
fn test_get_mut() {
655+
let cache = ShardedSieveCache::new(100).unwrap();
656+
cache.insert("key".to_string(), "value".to_string());
657+
658+
// Modify the value in-place
659+
let modified = cache.get_mut(&"key".to_string(), |value| {
660+
*value = "new_value".to_string();
661+
});
662+
assert!(modified);
663+
664+
// Verify the value was updated
665+
assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
666+
667+
// Try to modify a non-existent key
668+
let modified = cache.get_mut(&"missing".to_string(), |_| {
669+
panic!("This should not be called");
670+
});
671+
assert!(!modified);
672+
}
673+
674+
#[test]
675+
fn test_get_mut_concurrent() {
676+
let cache = Arc::new(ShardedSieveCache::with_shards(100, 8).unwrap());
677+
678+
// Insert initial values
679+
for i in 0..10 {
680+
cache.insert(format!("key{}", i), 0);
681+
}
682+
683+
let mut handles = vec![];
684+
685+
// Spawn 5 threads that modify values concurrently
686+
for _ in 0..5 {
687+
let cache_clone = Arc::clone(&cache);
688+
689+
let handle = thread::spawn(move || {
690+
for i in 0..10 {
691+
for _ in 0..100 {
692+
cache_clone.get_mut(&format!("key{}", i), |value| {
693+
*value += 1;
694+
});
695+
}
696+
}
697+
});
698+
699+
handles.push(handle);
700+
}
701+
702+
// Wait for all threads to complete
703+
for handle in handles {
704+
handle.join().unwrap();
705+
}
706+
707+
// Each key should have been incremented 500 times (5 threads * 100 increments each)
708+
for i in 0..10 {
709+
assert_eq!(cache.get(&format!("key{}", i)), Some(500));
710+
}
711+
}
614712
}

src/sync.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,43 @@ where
163163
guard.get(key).cloned()
164164
}
165165

166+
/// Gets a mutable reference to the value in the cache mapped to by `key` via a callback function.
167+
///
168+
/// If no value exists for `key`, the callback will not be invoked and this returns `false`.
169+
/// Otherwise, the callback is invoked with a mutable reference to the value and this returns `true`.
170+
///
171+
/// This operation marks the entry as "visited" in the SIEVE algorithm,
172+
/// which affects eviction decisions.
173+
///
174+
/// # Examples
175+
///
176+
/// ```
177+
/// # use sieve_cache::SyncSieveCache;
178+
/// let cache = SyncSieveCache::new(100).unwrap();
179+
/// cache.insert("key".to_string(), "value".to_string());
180+
///
181+
/// // Modify the value in-place
182+
/// cache.get_mut(&"key".to_string(), |value| {
183+
/// *value = "new_value".to_string();
184+
/// });
185+
///
186+
/// assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
187+
/// ```
188+
pub fn get_mut<Q, F>(&self, key: &Q, f: F) -> bool
189+
where
190+
Q: Hash + Eq + ?Sized,
191+
K: Borrow<Q>,
192+
F: FnOnce(&mut V),
193+
{
194+
let mut guard = self.locked_cache();
195+
if let Some(value) = guard.get_mut(key) {
196+
f(value);
197+
true
198+
} else {
199+
false
200+
}
201+
}
202+
166203
/// Maps `key` to `value` in the cache, possibly evicting old entries.
167204
///
168205
/// This method returns `true` when this is a new entry, and `false` if an existing entry was
@@ -348,4 +385,25 @@ mod tests {
348385

349386
assert_eq!(cache.len(), 3);
350387
}
388+
389+
#[test]
390+
fn test_get_mut() {
391+
let cache = SyncSieveCache::new(100).unwrap();
392+
cache.insert("key".to_string(), "value".to_string());
393+
394+
// Modify the value in-place
395+
let modified = cache.get_mut(&"key".to_string(), |value| {
396+
*value = "new_value".to_string();
397+
});
398+
assert!(modified);
399+
400+
// Verify the value was updated
401+
assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
402+
403+
// Try to modify a non-existent key
404+
let modified = cache.get_mut(&"missing".to_string(), |_| {
405+
panic!("This should not be called");
406+
});
407+
assert!(!modified);
408+
}
351409
}

0 commit comments

Comments
 (0)