@@ -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}
0 commit comments