From fd68bd611b3a0b3c1402bb69b00d937b4e425e73 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 12:13:52 +0200 Subject: [PATCH 01/23] Add non-blocking methods --- src/sync.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/sync.rs b/src/sync.rs index df77b40..d9b479e 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -256,6 +256,19 @@ impl< shard.read().get(hash, key).cloned() } + /// Try fetches an item from the cache whose key is `key`. + pub fn try_get(&self, key: &Q) -> Option + where + Q: Hash + Equivalent + ?Sized, + { + let (shard, hash) = self.shard_for(key)?; + shard + .try_read() + .as_ref() + .and_then(|r| r.get(hash, key)) + .cloned() + } + /// Peeks an item from the cache whose key is `key`. /// Contrary to gets, peeks don't alter the key "hotness". pub fn peek(&self, key: &Q) -> Option @@ -266,6 +279,20 @@ impl< shard.read().peek(hash, key).cloned() } + /// Try peeks an item from the cache whose key is `key`. + /// Contrary to gets, peeks don't alter the key "hotness". + pub fn try_peek(&self, key: &Q) -> Option + where + Q: Hash + Equivalent + ?Sized, + { + let (shard, hash) = self.shard_for(key)?; + shard + .try_read() + .as_ref() + .and_then(|r| r.peek(hash, key)) + .cloned() + } + /// Remove an item from the cache whose key is `key`. /// Returns the removed entry, if any. pub fn remove(&self, key: &Q) -> Option<(Key, Val)> @@ -337,6 +364,16 @@ impl< self.lifecycle.end_request(lcs); } + /// Try inserts an item in the cache with key `key`. + pub fn try_insert(&self, key: Key, value: Val) -> bool { + if let Some(lcs) = self.try_insert_with_lifecycle(key, value) { + self.lifecycle.end_request(lcs); + return true; + } + + false + } + /// Inserts an item in the cache with key `key`. pub fn insert_with_lifecycle(&self, key: Key, value: Val) -> L::RequestState { let mut lcs = self.lifecycle.begin_request(); @@ -349,6 +386,21 @@ impl< lcs } + /// Try inserts an item in the cache with key `key`. + pub fn try_insert_with_lifecycle(&self, key: Key, value: Val) -> Option { + let mut lcs = self.lifecycle.begin_request(); + let (shard, hash) = self.shard_for(&key).unwrap(); + + if let Some(mut guard) = shard.try_write() { + let result = guard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); + // result cannot err with the Insert strategy + debug_assert!(result.is_ok()); + return Some(lcs); + } + + None + } + /// Clear all items from the cache pub fn clear(&self) { for s in self.shards.iter() { From 9fa1a83d23d25463e2cadacacf4f4f4a3a4aaecd Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 12:20:50 +0200 Subject: [PATCH 02/23] Do not leak lcs --- src/sync.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index d9b479e..4c8d391 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -256,7 +256,8 @@ impl< shard.read().get(hash, key).cloned() } - /// Try fetches an item from the cache whose key is `key`. + /// Attempts to fetch an item from the cache whose key is `key`. + /// Returns `None` if the key is not present or the shard lock is contended. pub fn try_get(&self, key: &Q) -> Option where Q: Hash + Equivalent + ?Sized, @@ -279,8 +280,9 @@ impl< shard.read().peek(hash, key).cloned() } - /// Try peeks an item from the cache whose key is `key`. + /// Attempts to peek an item from the cache whose key is `key`. /// Contrary to gets, peeks don't alter the key "hotness". + /// Returns `None` if the key is not present or the shard lock is contended. pub fn try_peek(&self, key: &Q) -> Option where Q: Hash + Equivalent + ?Sized, @@ -364,7 +366,8 @@ impl< self.lifecycle.end_request(lcs); } - /// Try inserts an item in the cache with key `key`. + /// Attempts to insert an item in the cache with key `key`. + /// Returns `true` if the item was inserted, or `false` if the shard lock was contended. pub fn try_insert(&self, key: Key, value: Val) -> bool { if let Some(lcs) = self.try_insert_with_lifecycle(key, value) { self.lifecycle.end_request(lcs); @@ -386,12 +389,13 @@ impl< lcs } - /// Try inserts an item in the cache with key `key`. + /// Attempts to insert an item in the cache with key `key`. + /// Returns `Some(RequestState)` if the item was inserted, or `None` if the shard lock was contended. pub fn try_insert_with_lifecycle(&self, key: Key, value: Val) -> Option { - let mut lcs = self.lifecycle.begin_request(); let (shard, hash) = self.shard_for(&key).unwrap(); if let Some(mut guard) = shard.try_write() { + let mut lcs = self.lifecycle.begin_request(); let result = guard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); // result cannot err with the Insert strategy debug_assert!(result.is_ok()); From 7c6420cdb72ac259731372762e25402b0402ae81 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 12:42:04 +0200 Subject: [PATCH 03/23] Dedicated result --- src/rw_lock.rs | 28 ++++++++++++++++++++ src/sync.rs | 70 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/src/rw_lock.rs b/src/rw_lock.rs index 278f5b7..f52bf30 100644 --- a/src/rw_lock.rs +++ b/src/rw_lock.rs @@ -131,6 +131,34 @@ impl RwLock { self.0.write().unwrap() }) } + + /// Attempts to acquire this lock with shared read access. + /// + /// Returns `Some(guard)` if the lock was acquired, or `None` if it is + /// currently held by a writer. + #[inline] + pub fn try_read(&self) -> Option> { + #[cfg(feature = "parking_lot")] + { + self.0.try_read().map(RwLockReadGuard) + } + #[cfg(not(feature = "parking_lot"))] + self.0.try_read().ok().map(RwLockReadGuard) + } + + /// Attempts to acquire this lock with exclusive write access. + /// + /// Returns `Some(guard)` if the lock was acquired, or `None` if it is + /// currently held by any readers or a writer. + #[inline] + pub fn try_write(&self) -> Option> { + #[cfg(feature = "parking_lot")] + { + self.0.try_write().map(RwLockWriteGuard) + } + #[cfg(not(feature = "parking_lot"))] + self.0.try_write().ok().map(RwLockWriteGuard) + } } impl Deref for RwLockReadGuard<'_, T> { diff --git a/src/sync.rs b/src/sync.rs index 4c8d391..5f11e57 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -18,6 +18,16 @@ use crate::shard::EntryOrPlaceholder; pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard}; use crate::sync_placeholder::{JoinFuture, JoinResult}; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TryResult { + /// The value was found and is returned. + Found(Val), + /// The value was not found, but the shard lock was successfully acquired. + NotFound, + /// The shard lock could not be acquired without blocking. + Contended, +} + /// A concurrent cache /// /// The concurrent cache is internally composed of equally sized shards, each of which is independently @@ -257,17 +267,23 @@ impl< } /// Attempts to fetch an item from the cache whose key is `key`. - /// Returns `None` if the key is not present or the shard lock is contended. - pub fn try_get(&self, key: &Q) -> Option + /// Returns `Ok(Some(value))` if found, `Ok(None)` if the key is absent, + /// or `Err(Contended)` if the shard lock could not be acquired without blocking. + pub fn try_get(&self, key: &Q) -> TryResult where Q: Hash + Equivalent + ?Sized, { - let (shard, hash) = self.shard_for(key)?; - shard - .try_read() - .as_ref() - .and_then(|r| r.get(hash, key)) - .cloned() + let Some((shard, hash)) = self.shard_for(key) else { + return TryResult::NotFound; + }; + match shard.try_read() { + Some(guard) => guard + .get(hash, key) + .cloned() + .map(TryResult::Found) + .unwrap_or(TryResult::NotFound), + None => TryResult::Contended, + } } /// Peeks an item from the cache whose key is `key`. @@ -282,17 +298,23 @@ impl< /// Attempts to peek an item from the cache whose key is `key`. /// Contrary to gets, peeks don't alter the key "hotness". - /// Returns `None` if the key is not present or the shard lock is contended. - pub fn try_peek(&self, key: &Q) -> Option + /// Returns `Ok(Some(value))` if found, `Ok(None)` if the key is absent, + /// or `Err(Contended)` if the shard lock could not be acquired without blocking. + pub fn try_peek(&self, key: &Q) -> TryResult where Q: Hash + Equivalent + ?Sized, { - let (shard, hash) = self.shard_for(key)?; - shard - .try_read() - .as_ref() - .and_then(|r| r.peek(hash, key)) - .cloned() + let Some((shard, hash)) = self.shard_for(key) else { + return TryResult::NotFound; + }; + match shard.try_read() { + Some(guard) => guard + .peek(hash, key) + .cloned() + .map(TryResult::Found) + .unwrap_or(TryResult::NotFound), + None => TryResult::Contended, + } } /// Remove an item from the cache whose key is `key`. @@ -305,6 +327,22 @@ impl< shard.write().remove(hash, key) } + pub fn try_remove(&self, key: &Q) -> TryResult<(Key, Val)> + where + Q: Hash + Equivalent + ?Sized, + { + let (shard, hash) = self.shard_for(key).unwrap(); + shard + .try_write() + .map(|mut guard| { + guard + .remove(hash, key) + .map(TryResult::Found) + .unwrap_or(TryResult::NotFound) + }) + .unwrap_or(TryResult::Contended) + } + /// Remove an item from the cache whose key is `key` if `f(&value)` returns `true` for that entry. /// Compared to peek and remove, this method guarantees that no new value was inserted in-between. /// From f2c814b8d7ffccf42f395e65c64d41f88f807e88 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 12:53:14 +0200 Subject: [PATCH 04/23] More --- src/sync.rs | 102 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index 5f11e57..f91d4e4 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -257,6 +257,27 @@ impl< .is_some_and(|(shard, hash)| shard.read().contains(hash, key)) } + /// Attempts to check if a key exists in the cache without blocking. + /// Returns [`TryResult::Found(true)`] if present, [`TryResult::NotFound`] if absent, + /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. + pub fn try_contains_key(&self, key: &Q) -> TryResult + where + Q: Hash + Equivalent + ?Sized, + { + let Some((shard, hash)) = self.shard_for(key) else { + return TryResult::NotFound; + }; + shard + .try_read() + .map(|guard| { + guard + .contains(hash, key) + .then(|| TryResult::Found(true)) + .unwrap_or(TryResult::NotFound) + }) + .unwrap_or(TryResult::Contended) + } + /// Fetches an item from the cache whose key is `key`. pub fn get(&self, key: &Q) -> Option where @@ -267,8 +288,8 @@ impl< } /// Attempts to fetch an item from the cache whose key is `key`. - /// Returns `Ok(Some(value))` if found, `Ok(None)` if the key is absent, - /// or `Err(Contended)` if the shard lock could not be acquired without blocking. + /// Returns [`TryResult::Found`] if the key is present, [`TryResult::NotFound`] if absent, + /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. pub fn try_get(&self, key: &Q) -> TryResult where Q: Hash + Equivalent + ?Sized, @@ -276,14 +297,16 @@ impl< let Some((shard, hash)) = self.shard_for(key) else { return TryResult::NotFound; }; - match shard.try_read() { - Some(guard) => guard - .get(hash, key) - .cloned() - .map(TryResult::Found) - .unwrap_or(TryResult::NotFound), - None => TryResult::Contended, - } + shard + .try_read() + .map(|guard| { + guard + .get(hash, key) + .cloned() + .map(TryResult::Found) + .unwrap_or(TryResult::NotFound) + }) + .unwrap_or(TryResult::Contended) } /// Peeks an item from the cache whose key is `key`. @@ -298,8 +321,8 @@ impl< /// Attempts to peek an item from the cache whose key is `key`. /// Contrary to gets, peeks don't alter the key "hotness". - /// Returns `Ok(Some(value))` if found, `Ok(None)` if the key is absent, - /// or `Err(Contended)` if the shard lock could not be acquired without blocking. + /// Returns [`TryResult::Found`] if the key is present, [`TryResult::NotFound`] if absent, + /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. pub fn try_peek(&self, key: &Q) -> TryResult where Q: Hash + Equivalent + ?Sized, @@ -307,14 +330,16 @@ impl< let Some((shard, hash)) = self.shard_for(key) else { return TryResult::NotFound; }; - match shard.try_read() { - Some(guard) => guard - .peek(hash, key) - .cloned() - .map(TryResult::Found) - .unwrap_or(TryResult::NotFound), - None => TryResult::Contended, - } + shard + .try_read() + .map(|guard| { + guard + .peek(hash, key) + .cloned() + .map(TryResult::Found) + .unwrap_or(TryResult::NotFound) + }) + .unwrap_or(TryResult::Contended) } /// Remove an item from the cache whose key is `key`. @@ -327,11 +352,17 @@ impl< shard.write().remove(hash, key) } + /// Attempts to remove an item from the cache whose key is `key`. + /// Returns [`TryResult::Found`] with the removed entry if present, [`TryResult::NotFound`] if absent, + /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. pub fn try_remove(&self, key: &Q) -> TryResult<(Key, Val)> where Q: Hash + Equivalent + ?Sized, { - let (shard, hash) = self.shard_for(key).unwrap(); + let Some((shard, hash)) = self.shard_for(key) else { + return TryResult::NotFound; + }; + shard .try_write() .map(|mut guard| { @@ -407,7 +438,7 @@ impl< /// Attempts to insert an item in the cache with key `key`. /// Returns `true` if the item was inserted, or `false` if the shard lock was contended. pub fn try_insert(&self, key: Key, value: Val) -> bool { - if let Some(lcs) = self.try_insert_with_lifecycle(key, value) { + if let TryResult::Found(lcs) = self.try_insert_with_lifecycle(key, value) { self.lifecycle.end_request(lcs); return true; } @@ -428,19 +459,22 @@ impl< } /// Attempts to insert an item in the cache with key `key`. - /// Returns `Some(RequestState)` if the item was inserted, or `None` if the shard lock was contended. - pub fn try_insert_with_lifecycle(&self, key: Key, value: Val) -> Option { - let (shard, hash) = self.shard_for(&key).unwrap(); - - if let Some(mut guard) = shard.try_write() { - let mut lcs = self.lifecycle.begin_request(); - let result = guard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); - // result cannot err with the Insert strategy - debug_assert!(result.is_ok()); - return Some(lcs); - } + /// Returns [`TryResult::Found`] if the item was inserted, or [`TryResult::Contended`] if the shard lock was contended. + pub fn try_insert_with_lifecycle(&self, key: Key, value: Val) -> TryResult { + let Some((shard, hash)) = self.shard_for(&key) else { + return TryResult::NotFound; + }; - None + shard + .try_write() + .map(|mut guard| { + let mut lcs = self.lifecycle.begin_request(); + let result = guard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); + // result cannot err with the Insert strategy + debug_assert!(result.is_ok()); + TryResult::Found(lcs) + }) + .unwrap_or(TryResult::Contended) } /// Clear all items from the cache From 844072d6def401be9c71bc11d09d1facc71319b4 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 13:01:05 +0200 Subject: [PATCH 05/23] Simplify --- src/sync.rs | 88 +++++++++++++++++++++-------------------------------- 1 file changed, 34 insertions(+), 54 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index f91d4e4..c1b1477 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -19,11 +19,9 @@ pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, Placeho use crate::sync_placeholder::{JoinFuture, JoinResult}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TryResult { +pub enum ContendedResult { /// The value was found and is returned. - Found(Val), - /// The value was not found, but the shard lock was successfully acquired. - NotFound, + Ok(Val), /// The shard lock could not be acquired without blocking. Contended, } @@ -260,22 +258,18 @@ impl< /// Attempts to check if a key exists in the cache without blocking. /// Returns [`TryResult::Found(true)`] if present, [`TryResult::NotFound`] if absent, /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. - pub fn try_contains_key(&self, key: &Q) -> TryResult + pub fn try_contains_key(&self, key: &Q) -> ContendedResult where Q: Hash + Equivalent + ?Sized, { let Some((shard, hash)) = self.shard_for(key) else { - return TryResult::NotFound; + return ContendedResult::Ok(false); }; + shard .try_read() - .map(|guard| { - guard - .contains(hash, key) - .then(|| TryResult::Found(true)) - .unwrap_or(TryResult::NotFound) - }) - .unwrap_or(TryResult::Contended) + .map(|guard| ContendedResult::Ok(guard.contains(hash, key))) + .unwrap_or(ContendedResult::Contended) } /// Fetches an item from the cache whose key is `key`. @@ -290,23 +284,17 @@ impl< /// Attempts to fetch an item from the cache whose key is `key`. /// Returns [`TryResult::Found`] if the key is present, [`TryResult::NotFound`] if absent, /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. - pub fn try_get(&self, key: &Q) -> TryResult + pub fn try_get(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, { let Some((shard, hash)) = self.shard_for(key) else { - return TryResult::NotFound; + return ContendedResult::Ok(None); }; shard .try_read() - .map(|guard| { - guard - .get(hash, key) - .cloned() - .map(TryResult::Found) - .unwrap_or(TryResult::NotFound) - }) - .unwrap_or(TryResult::Contended) + .map(|guard| ContendedResult::Ok(guard.get(hash, key).cloned())) + .unwrap_or(ContendedResult::Contended) } /// Peeks an item from the cache whose key is `key`. @@ -323,23 +311,17 @@ impl< /// Contrary to gets, peeks don't alter the key "hotness". /// Returns [`TryResult::Found`] if the key is present, [`TryResult::NotFound`] if absent, /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. - pub fn try_peek(&self, key: &Q) -> TryResult + pub fn try_peek(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, { let Some((shard, hash)) = self.shard_for(key) else { - return TryResult::NotFound; + return ContendedResult::Ok(None); }; shard .try_read() - .map(|guard| { - guard - .peek(hash, key) - .cloned() - .map(TryResult::Found) - .unwrap_or(TryResult::NotFound) - }) - .unwrap_or(TryResult::Contended) + .map(|guard| ContendedResult::Ok(guard.peek(hash, key).cloned())) + .unwrap_or(ContendedResult::Contended) } /// Remove an item from the cache whose key is `key`. @@ -355,23 +337,18 @@ impl< /// Attempts to remove an item from the cache whose key is `key`. /// Returns [`TryResult::Found`] with the removed entry if present, [`TryResult::NotFound`] if absent, /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. - pub fn try_remove(&self, key: &Q) -> TryResult<(Key, Val)> + pub fn try_remove(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, { let Some((shard, hash)) = self.shard_for(key) else { - return TryResult::NotFound; + return ContendedResult::Ok(None); }; shard .try_write() - .map(|mut guard| { - guard - .remove(hash, key) - .map(TryResult::Found) - .unwrap_or(TryResult::NotFound) - }) - .unwrap_or(TryResult::Contended) + .map(|mut guard| ContendedResult::Ok(guard.remove(hash, key))) + .unwrap_or(ContendedResult::Contended) } /// Remove an item from the cache whose key is `key` if `f(&value)` returns `true` for that entry. @@ -437,13 +414,14 @@ impl< /// Attempts to insert an item in the cache with key `key`. /// Returns `true` if the item was inserted, or `false` if the shard lock was contended. - pub fn try_insert(&self, key: Key, value: Val) -> bool { - if let TryResult::Found(lcs) = self.try_insert_with_lifecycle(key, value) { - self.lifecycle.end_request(lcs); - return true; + pub fn try_insert(&self, key: Key, value: Val) -> ContendedResult<()> { + match self.try_insert_with_lifecycle(key, value) { + ContendedResult::Ok(lcs) => { + self.lifecycle.end_request(lcs); + ContendedResult::Ok(()) + } + ContendedResult::Contended => ContendedResult::Contended, } - - false } /// Inserts an item in the cache with key `key`. @@ -460,10 +438,12 @@ impl< /// Attempts to insert an item in the cache with key `key`. /// Returns [`TryResult::Found`] if the item was inserted, or [`TryResult::Contended`] if the shard lock was contended. - pub fn try_insert_with_lifecycle(&self, key: Key, value: Val) -> TryResult { - let Some((shard, hash)) = self.shard_for(&key) else { - return TryResult::NotFound; - }; + pub fn try_insert_with_lifecycle( + &self, + key: Key, + value: Val, + ) -> ContendedResult { + let (shard, hash) = self.shard_for(&key).unwrap(); shard .try_write() @@ -472,9 +452,9 @@ impl< let result = guard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); // result cannot err with the Insert strategy debug_assert!(result.is_ok()); - TryResult::Found(lcs) + ContendedResult::Ok(lcs) }) - .unwrap_or(TryResult::Contended) + .unwrap_or(ContendedResult::Contended) } /// Clear all items from the cache From 8eb51f56f2655892ecb62d632c7bb43f98d6f014 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 13:03:44 +0200 Subject: [PATCH 06/23] Cleanup + some docs --- src/rw_lock.rs | 28 ---------------------------- src/sync.rs | 31 +++++++++++++++++++++---------- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/src/rw_lock.rs b/src/rw_lock.rs index f52bf30..278f5b7 100644 --- a/src/rw_lock.rs +++ b/src/rw_lock.rs @@ -131,34 +131,6 @@ impl RwLock { self.0.write().unwrap() }) } - - /// Attempts to acquire this lock with shared read access. - /// - /// Returns `Some(guard)` if the lock was acquired, or `None` if it is - /// currently held by a writer. - #[inline] - pub fn try_read(&self) -> Option> { - #[cfg(feature = "parking_lot")] - { - self.0.try_read().map(RwLockReadGuard) - } - #[cfg(not(feature = "parking_lot"))] - self.0.try_read().ok().map(RwLockReadGuard) - } - - /// Attempts to acquire this lock with exclusive write access. - /// - /// Returns `Some(guard)` if the lock was acquired, or `None` if it is - /// currently held by any readers or a writer. - #[inline] - pub fn try_write(&self) -> Option> { - #[cfg(feature = "parking_lot")] - { - self.0.try_write().map(RwLockWriteGuard) - } - #[cfg(not(feature = "parking_lot"))] - self.0.try_write().ok().map(RwLockWriteGuard) - } } impl Deref for RwLockReadGuard<'_, T> { diff --git a/src/sync.rs b/src/sync.rs index c1b1477..ea4229b 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -26,6 +26,16 @@ pub enum ContendedResult { Contended, } +impl ContendedResult { + pub fn is_ok(&self) -> bool { + matches!(self, ContendedResult::Ok(_)) + } + + pub fn is_contended(&self) -> bool { + matches!(self, ContendedResult::Contended) + } +} + /// A concurrent cache /// /// The concurrent cache is internally composed of equally sized shards, each of which is independently @@ -256,8 +266,8 @@ impl< } /// Attempts to check if a key exists in the cache without blocking. - /// Returns [`TryResult::Found(true)`] if present, [`TryResult::NotFound`] if absent, - /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. + /// Returns [`ContendedResult::Ok(true)`] if present, [`ContendedResult::Ok(false)`] if absent, + /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. pub fn try_contains_key(&self, key: &Q) -> ContendedResult where Q: Hash + Equivalent + ?Sized, @@ -282,8 +292,8 @@ impl< } /// Attempts to fetch an item from the cache whose key is `key`. - /// Returns [`TryResult::Found`] if the key is present, [`TryResult::NotFound`] if absent, - /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. + /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, + /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. pub fn try_get(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, @@ -309,8 +319,8 @@ impl< /// Attempts to peek an item from the cache whose key is `key`. /// Contrary to gets, peeks don't alter the key "hotness". - /// Returns [`TryResult::Found`] if the key is present, [`TryResult::NotFound`] if absent, - /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. + /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, + /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. pub fn try_peek(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, @@ -335,8 +345,8 @@ impl< } /// Attempts to remove an item from the cache whose key is `key`. - /// Returns [`TryResult::Found`] with the removed entry if present, [`TryResult::NotFound`] if absent, - /// or [`TryResult::Contended`] if the shard lock could not be acquired without blocking. + /// Returns [`ContendedResult::Ok(Some(entry))`] with the removed entry if present, [`ContendedResult::Ok(None)`] if absent, + /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. pub fn try_remove(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, @@ -413,7 +423,7 @@ impl< } /// Attempts to insert an item in the cache with key `key`. - /// Returns `true` if the item was inserted, or `false` if the shard lock was contended. + /// Returns [`ContendedResult::Ok`] if the item was inserted, or [`ContendedResult::Contended`] if the shard lock was contended. pub fn try_insert(&self, key: Key, value: Val) -> ContendedResult<()> { match self.try_insert_with_lifecycle(key, value) { ContendedResult::Ok(lcs) => { @@ -437,7 +447,8 @@ impl< } /// Attempts to insert an item in the cache with key `key`. - /// Returns [`TryResult::Found`] if the item was inserted, or [`TryResult::Contended`] if the shard lock was contended. + /// Returns [`ContendedResult::Ok`] with the lifecycle request state if the item was inserted, + /// or [`ContendedResult::Contended`] if the shard lock was contended. pub fn try_insert_with_lifecycle( &self, key: Key, From 354b745ca90c61b0f2daa34cf94446bd37e3b5fa Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 13:27:27 +0200 Subject: [PATCH 07/23] Docs --- src/sync.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index ea4229b..cad9e51 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -18,11 +18,13 @@ use crate::shard::EntryOrPlaceholder; pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard}; use crate::sync_placeholder::{JoinFuture, JoinResult}; +/// The result of a non-blocking cache operation. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ContendedResult { - /// The value was found and is returned. + /// The operation succeeded. For read operations the inner value holds the lookup result; + /// for write operations it holds the lifecycle request state (or `()` for [`Cache::try_insert`]). Ok(Val), - /// The shard lock could not be acquired without blocking. + /// The shard lock could not be acquired without blocking. The operation was not performed. Contended, } From 3d8fdf9510a65cefb5b3cbe9b7dfea9b26e8a747 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 13:50:55 +0200 Subject: [PATCH 08/23] Use a feature flag --- Cargo.toml | 1 + src/rw_lock.rs | 34 ++++++++++++++++++++++++++++++++++ src/sync.rs | 15 +++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f6a8eef..7e35418 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ rust-version = "1.71" [features] default = ["ahash", "parking_lot"] +non-blocking = [] sharded-lock = ["dep:crossbeam-utils"] shuttle = ["dep:shuttle"] stats = [] diff --git a/src/rw_lock.rs b/src/rw_lock.rs index 278f5b7..4da45e9 100644 --- a/src/rw_lock.rs +++ b/src/rw_lock.rs @@ -105,6 +105,40 @@ impl RwLock { }) } + /// Attempts to acquire this `RwLock` with shared read access without blocking. + /// + /// Returns `Some(guard)` if the lock was acquired, or `None` if it is already + /// held by a writer. + #[cfg(feature = "non-blocking")] + #[inline] + pub fn try_read(&self) -> Option> { + #[cfg(feature = "parking_lot")] + { + self.0.try_read().map(RwLockReadGuard) + } + #[cfg(not(feature = "parking_lot"))] + { + self.0.try_read().ok().map(RwLockReadGuard) + } + } + + /// Attempts to acquire this `RwLock` with exclusive write access without blocking. + /// + /// Returns `Some(guard)` if the lock was acquired, or `None` if it is already + /// held by any readers or a writer. + #[cfg(feature = "non-blocking")] + #[inline] + pub fn try_write(&self) -> Option> { + #[cfg(feature = "parking_lot")] + { + self.0.try_write().map(RwLockWriteGuard) + } + #[cfg(not(feature = "parking_lot"))] + { + self.0.try_write().ok().map(RwLockWriteGuard) + } + } + /// Locks this `RwLock` with exclusive write access, blocking the current /// thread until it can be acquired. /// diff --git a/src/sync.rs b/src/sync.rs index cad9e51..f0009b9 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -18,6 +18,7 @@ use crate::shard::EntryOrPlaceholder; pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard}; use crate::sync_placeholder::{JoinFuture, JoinResult}; +#[cfg(feature = "non-blocking")] /// The result of a non-blocking cache operation. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ContendedResult { @@ -28,9 +29,13 @@ pub enum ContendedResult { Contended, } +#[cfg(feature = "non-blocking")] impl ContendedResult { - pub fn is_ok(&self) -> bool { - matches!(self, ContendedResult::Ok(_)) + pub fn ok(self) -> Option { + match self { + ContendedResult::Ok(val) => Some(val), + ContendedResult::Contended => None, + } } pub fn is_contended(&self) -> bool { @@ -270,6 +275,7 @@ impl< /// Attempts to check if a key exists in the cache without blocking. /// Returns [`ContendedResult::Ok(true)`] if present, [`ContendedResult::Ok(false)`] if absent, /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. + #[cfg(feature = "non-blocking")] pub fn try_contains_key(&self, key: &Q) -> ContendedResult where Q: Hash + Equivalent + ?Sized, @@ -296,6 +302,7 @@ impl< /// Attempts to fetch an item from the cache whose key is `key`. /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. + #[cfg(feature = "non-blocking")] pub fn try_get(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, @@ -323,6 +330,7 @@ impl< /// Contrary to gets, peeks don't alter the key "hotness". /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. + #[cfg(feature = "non-blocking")] pub fn try_peek(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, @@ -349,6 +357,7 @@ impl< /// Attempts to remove an item from the cache whose key is `key`. /// Returns [`ContendedResult::Ok(Some(entry))`] with the removed entry if present, [`ContendedResult::Ok(None)`] if absent, /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. + #[cfg(feature = "non-blocking")] pub fn try_remove(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, @@ -426,6 +435,7 @@ impl< /// Attempts to insert an item in the cache with key `key`. /// Returns [`ContendedResult::Ok`] if the item was inserted, or [`ContendedResult::Contended`] if the shard lock was contended. + #[cfg(feature = "non-blocking")] pub fn try_insert(&self, key: Key, value: Val) -> ContendedResult<()> { match self.try_insert_with_lifecycle(key, value) { ContendedResult::Ok(lcs) => { @@ -451,6 +461,7 @@ impl< /// Attempts to insert an item in the cache with key `key`. /// Returns [`ContendedResult::Ok`] with the lifecycle request state if the item was inserted, /// or [`ContendedResult::Contended`] if the shard lock was contended. + #[cfg(feature = "non-blocking")] pub fn try_insert_with_lifecycle( &self, key: Key, From 6eaaea043d1c888e6cc80fdd5788a0f597d09710 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 12 Apr 2026 22:11:53 +0200 Subject: [PATCH 09/23] Posion lock --- src/rw_lock.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/rw_lock.rs b/src/rw_lock.rs index 4da45e9..4cf4a4d 100644 --- a/src/rw_lock.rs +++ b/src/rw_lock.rs @@ -118,7 +118,11 @@ impl RwLock { } #[cfg(not(feature = "parking_lot"))] { - self.0.try_read().ok().map(RwLockReadGuard) + match self.0.try_read() { + Ok(guard) => Some(RwLockReadGuard(guard)), + Err(std::sync::TryLockError::WouldBlock) => None, + Err(std::sync::TryLockError::Poisoned(err)) => panic!("{}", err), + } } } @@ -135,7 +139,11 @@ impl RwLock { } #[cfg(not(feature = "parking_lot"))] { - self.0.try_write().ok().map(RwLockWriteGuard) + match self.0.try_write() { + Ok(guard) => Some(RwLockWriteGuard(guard)), + Err(std::sync::TryLockError::WouldBlock) => None, + Err(std::sync::TryLockError::Poisoned(err)) => panic!("{}", err), + } } } From 8282733292450c5cbde2fcfaa92fad8635b00ba3 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Mon, 13 Apr 2026 09:50:31 +0200 Subject: [PATCH 10/23] Remove feature and more tests --- Cargo.toml | 1 - src/rw_lock.rs | 2 - src/sync.rs | 137 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 129 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7e35418..f6a8eef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ rust-version = "1.71" [features] default = ["ahash", "parking_lot"] -non-blocking = [] sharded-lock = ["dep:crossbeam-utils"] shuttle = ["dep:shuttle"] stats = [] diff --git a/src/rw_lock.rs b/src/rw_lock.rs index 4cf4a4d..cc8e81c 100644 --- a/src/rw_lock.rs +++ b/src/rw_lock.rs @@ -109,7 +109,6 @@ impl RwLock { /// /// Returns `Some(guard)` if the lock was acquired, or `None` if it is already /// held by a writer. - #[cfg(feature = "non-blocking")] #[inline] pub fn try_read(&self) -> Option> { #[cfg(feature = "parking_lot")] @@ -130,7 +129,6 @@ impl RwLock { /// /// Returns `Some(guard)` if the lock was acquired, or `None` if it is already /// held by any readers or a writer. - #[cfg(feature = "non-blocking")] #[inline] pub fn try_write(&self) -> Option> { #[cfg(feature = "parking_lot")] diff --git a/src/sync.rs b/src/sync.rs index f0009b9..e9f6f29 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -18,7 +18,6 @@ use crate::shard::EntryOrPlaceholder; pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard}; use crate::sync_placeholder::{JoinFuture, JoinResult}; -#[cfg(feature = "non-blocking")] /// The result of a non-blocking cache operation. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ContendedResult { @@ -29,7 +28,6 @@ pub enum ContendedResult { Contended, } -#[cfg(feature = "non-blocking")] impl ContendedResult { pub fn ok(self) -> Option { match self { @@ -275,7 +273,6 @@ impl< /// Attempts to check if a key exists in the cache without blocking. /// Returns [`ContendedResult::Ok(true)`] if present, [`ContendedResult::Ok(false)`] if absent, /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. - #[cfg(feature = "non-blocking")] pub fn try_contains_key(&self, key: &Q) -> ContendedResult where Q: Hash + Equivalent + ?Sized, @@ -302,7 +299,6 @@ impl< /// Attempts to fetch an item from the cache whose key is `key`. /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. - #[cfg(feature = "non-blocking")] pub fn try_get(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, @@ -330,7 +326,6 @@ impl< /// Contrary to gets, peeks don't alter the key "hotness". /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. - #[cfg(feature = "non-blocking")] pub fn try_peek(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, @@ -357,7 +352,6 @@ impl< /// Attempts to remove an item from the cache whose key is `key`. /// Returns [`ContendedResult::Ok(Some(entry))`] with the removed entry if present, [`ContendedResult::Ok(None)`] if absent, /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. - #[cfg(feature = "non-blocking")] pub fn try_remove(&self, key: &Q) -> ContendedResult> where Q: Hash + Equivalent + ?Sized, @@ -435,7 +429,6 @@ impl< /// Attempts to insert an item in the cache with key `key`. /// Returns [`ContendedResult::Ok`] if the item was inserted, or [`ContendedResult::Contended`] if the shard lock was contended. - #[cfg(feature = "non-blocking")] pub fn try_insert(&self, key: Key, value: Val) -> ContendedResult<()> { match self.try_insert_with_lifecycle(key, value) { ContendedResult::Ok(lcs) => { @@ -461,7 +454,6 @@ impl< /// Attempts to insert an item in the cache with key `key`. /// Returns [`ContendedResult::Ok`] with the lifecycle request state if the item was inserted, /// or [`ContendedResult::Contended`] if the shard lock was contended. - #[cfg(feature = "non-blocking")] pub fn try_insert_with_lifecycle( &self, key: Key, @@ -1631,4 +1623,133 @@ mod tests { } } } + + // --- Non-blocking method tests --- + + #[test] + fn test_contended_result_helpers() { + let ok: ContendedResult = ContendedResult::Ok(42); + assert!(!ok.is_contended()); + assert_eq!(ok.ok(), Some(42)); + + let contended: ContendedResult = ContendedResult::Contended; + assert!(contended.is_contended()); + assert_eq!(contended.ok(), None); + } + + #[test] + fn test_try_contains_key() { + let cache = Cache::new(100); + cache.insert(1, 10); + + assert_eq!(cache.try_contains_key(&1), ContendedResult::Ok(true)); + assert_eq!(cache.try_contains_key(&2), ContendedResult::Ok(false)); + } + + #[test] + fn test_try_contains_key_contended() { + let cache = Cache::new(100); + cache.insert(1, 10); + // Hold write locks on all shards so try_read is blocked. + let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); + assert_eq!(cache.try_contains_key(&1), ContendedResult::Contended); + } + + #[test] + fn test_try_get() { + let cache = Cache::new(100); + cache.insert(1, 10); + + assert_eq!(cache.try_get(&1), ContendedResult::Ok(Some(10))); + assert_eq!(cache.try_get(&2), ContendedResult::Ok(None)); + } + + #[test] + fn test_try_get_contended() { + let cache = Cache::new(100); + cache.insert(1, 10); + let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); + assert_eq!(cache.try_get(&1), ContendedResult::Contended); + } + + #[test] + fn test_try_peek() { + let cache = Cache::new(100); + cache.insert(1, 10); + + assert_eq!(cache.try_peek(&1), ContendedResult::Ok(Some(10))); + assert_eq!(cache.try_peek(&2), ContendedResult::Ok(None)); + } + + #[test] + fn test_try_peek_contended() { + let cache = Cache::new(100); + cache.insert(1, 10); + let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); + assert_eq!(cache.try_peek(&1), ContendedResult::Contended); + } + + #[test] + fn test_try_remove() { + let cache = Cache::new(100); + cache.insert(1, 10); + + assert_eq!(cache.try_remove(&1), ContendedResult::Ok(Some((1, 10)))); + assert_eq!(cache.try_remove(&1), ContendedResult::Ok(None)); + assert_eq!(cache.try_remove(&99), ContendedResult::Ok(None)); + } + + #[test] + fn test_try_remove_contended() { + let cache = Cache::new(100); + cache.insert(1, 10); + // Hold read locks on all shards so try_write is blocked. + let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); + assert_eq!(cache.try_remove(&1), ContendedResult::Contended); + drop(guards); + // Item must still be present since the remove did not happen. + assert_eq!(cache.get(&1), Some(10)); + } + + #[test] + fn test_try_insert() { + let cache = Cache::new(100); + + assert_eq!(cache.try_insert(1, 10), ContendedResult::Ok(())); + assert_eq!(cache.get(&1), Some(10)); + + // Insert same key overwrites the previous value. + assert_eq!(cache.try_insert(1, 20), ContendedResult::Ok(())); + assert_eq!(cache.get(&1), Some(20)); + } + + #[test] + fn test_try_insert_contended() { + let cache = Cache::new(100); + let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); + assert_eq!(cache.try_insert(1, 10), ContendedResult::Contended); + drop(guards); + assert_eq!(cache.get(&1), None); + } + + #[test] + fn test_try_insert_with_lifecycle() { + let cache = Cache::new(100); + + // Successful insert returns the lifecycle request state. + let result = cache.try_insert_with_lifecycle(1, 10); + assert!(!result.is_contended()); + let lcs = result.ok().unwrap(); + cache.lifecycle.end_request(lcs); + assert_eq!(cache.get(&1), Some(10)); + + // Contended when a read lock is held. + let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); + assert_eq!( + cache.try_insert_with_lifecycle(2, 20), + ContendedResult::Contended + ); + drop(guards); + assert_eq!(cache.get(&2), None); + } } From a844c26fc064a4433935dbda6f253bbc8d0e777b Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla <105630300+fsdvh@users.noreply.github.com> Date: Mon, 13 Apr 2026 10:39:10 +0200 Subject: [PATCH 11/23] Add non-blocking methods for sync cache (#1) Add non-blocking API for sync cache --- src/rw_lock.rs | 40 ++++++++ src/sync.rs | 253 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+) diff --git a/src/rw_lock.rs b/src/rw_lock.rs index 278f5b7..cc8e81c 100644 --- a/src/rw_lock.rs +++ b/src/rw_lock.rs @@ -105,6 +105,46 @@ impl RwLock { }) } + /// Attempts to acquire this `RwLock` with shared read access without blocking. + /// + /// Returns `Some(guard)` if the lock was acquired, or `None` if it is already + /// held by a writer. + #[inline] + pub fn try_read(&self) -> Option> { + #[cfg(feature = "parking_lot")] + { + self.0.try_read().map(RwLockReadGuard) + } + #[cfg(not(feature = "parking_lot"))] + { + match self.0.try_read() { + Ok(guard) => Some(RwLockReadGuard(guard)), + Err(std::sync::TryLockError::WouldBlock) => None, + Err(std::sync::TryLockError::Poisoned(err)) => panic!("{}", err), + } + } + } + + /// Attempts to acquire this `RwLock` with exclusive write access without blocking. + /// + /// Returns `Some(guard)` if the lock was acquired, or `None` if it is already + /// held by any readers or a writer. + #[inline] + pub fn try_write(&self) -> Option> { + #[cfg(feature = "parking_lot")] + { + self.0.try_write().map(RwLockWriteGuard) + } + #[cfg(not(feature = "parking_lot"))] + { + match self.0.try_write() { + Ok(guard) => Some(RwLockWriteGuard(guard)), + Err(std::sync::TryLockError::WouldBlock) => None, + Err(std::sync::TryLockError::Poisoned(err)) => panic!("{}", err), + } + } + } + /// Locks this `RwLock` with exclusive write access, blocking the current /// thread until it can be acquired. /// diff --git a/src/sync.rs b/src/sync.rs index df77b40..e9f6f29 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -18,6 +18,29 @@ use crate::shard::EntryOrPlaceholder; pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard}; use crate::sync_placeholder::{JoinFuture, JoinResult}; +/// The result of a non-blocking cache operation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ContendedResult { + /// The operation succeeded. For read operations the inner value holds the lookup result; + /// for write operations it holds the lifecycle request state (or `()` for [`Cache::try_insert`]). + Ok(Val), + /// The shard lock could not be acquired without blocking. The operation was not performed. + Contended, +} + +impl ContendedResult { + pub fn ok(self) -> Option { + match self { + ContendedResult::Ok(val) => Some(val), + ContendedResult::Contended => None, + } + } + + pub fn is_contended(&self) -> bool { + matches!(self, ContendedResult::Contended) + } +} + /// A concurrent cache /// /// The concurrent cache is internally composed of equally sized shards, each of which is independently @@ -247,6 +270,23 @@ impl< .is_some_and(|(shard, hash)| shard.read().contains(hash, key)) } + /// Attempts to check if a key exists in the cache without blocking. + /// Returns [`ContendedResult::Ok(true)`] if present, [`ContendedResult::Ok(false)`] if absent, + /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. + pub fn try_contains_key(&self, key: &Q) -> ContendedResult + where + Q: Hash + Equivalent + ?Sized, + { + let Some((shard, hash)) = self.shard_for(key) else { + return ContendedResult::Ok(false); + }; + + shard + .try_read() + .map(|guard| ContendedResult::Ok(guard.contains(hash, key))) + .unwrap_or(ContendedResult::Contended) + } + /// Fetches an item from the cache whose key is `key`. pub fn get(&self, key: &Q) -> Option where @@ -256,6 +296,22 @@ impl< shard.read().get(hash, key).cloned() } + /// Attempts to fetch an item from the cache whose key is `key`. + /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, + /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. + pub fn try_get(&self, key: &Q) -> ContendedResult> + where + Q: Hash + Equivalent + ?Sized, + { + let Some((shard, hash)) = self.shard_for(key) else { + return ContendedResult::Ok(None); + }; + shard + .try_read() + .map(|guard| ContendedResult::Ok(guard.get(hash, key).cloned())) + .unwrap_or(ContendedResult::Contended) + } + /// Peeks an item from the cache whose key is `key`. /// Contrary to gets, peeks don't alter the key "hotness". pub fn peek(&self, key: &Q) -> Option @@ -266,6 +322,23 @@ impl< shard.read().peek(hash, key).cloned() } + /// Attempts to peek an item from the cache whose key is `key`. + /// Contrary to gets, peeks don't alter the key "hotness". + /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, + /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. + pub fn try_peek(&self, key: &Q) -> ContendedResult> + where + Q: Hash + Equivalent + ?Sized, + { + let Some((shard, hash)) = self.shard_for(key) else { + return ContendedResult::Ok(None); + }; + shard + .try_read() + .map(|guard| ContendedResult::Ok(guard.peek(hash, key).cloned())) + .unwrap_or(ContendedResult::Contended) + } + /// Remove an item from the cache whose key is `key`. /// Returns the removed entry, if any. pub fn remove(&self, key: &Q) -> Option<(Key, Val)> @@ -276,6 +349,23 @@ impl< shard.write().remove(hash, key) } + /// Attempts to remove an item from the cache whose key is `key`. + /// Returns [`ContendedResult::Ok(Some(entry))`] with the removed entry if present, [`ContendedResult::Ok(None)`] if absent, + /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. + pub fn try_remove(&self, key: &Q) -> ContendedResult> + where + Q: Hash + Equivalent + ?Sized, + { + let Some((shard, hash)) = self.shard_for(key) else { + return ContendedResult::Ok(None); + }; + + shard + .try_write() + .map(|mut guard| ContendedResult::Ok(guard.remove(hash, key))) + .unwrap_or(ContendedResult::Contended) + } + /// Remove an item from the cache whose key is `key` if `f(&value)` returns `true` for that entry. /// Compared to peek and remove, this method guarantees that no new value was inserted in-between. /// @@ -337,6 +427,18 @@ impl< self.lifecycle.end_request(lcs); } + /// Attempts to insert an item in the cache with key `key`. + /// Returns [`ContendedResult::Ok`] if the item was inserted, or [`ContendedResult::Contended`] if the shard lock was contended. + pub fn try_insert(&self, key: Key, value: Val) -> ContendedResult<()> { + match self.try_insert_with_lifecycle(key, value) { + ContendedResult::Ok(lcs) => { + self.lifecycle.end_request(lcs); + ContendedResult::Ok(()) + } + ContendedResult::Contended => ContendedResult::Contended, + } + } + /// Inserts an item in the cache with key `key`. pub fn insert_with_lifecycle(&self, key: Key, value: Val) -> L::RequestState { let mut lcs = self.lifecycle.begin_request(); @@ -349,6 +451,28 @@ impl< lcs } + /// Attempts to insert an item in the cache with key `key`. + /// Returns [`ContendedResult::Ok`] with the lifecycle request state if the item was inserted, + /// or [`ContendedResult::Contended`] if the shard lock was contended. + pub fn try_insert_with_lifecycle( + &self, + key: Key, + value: Val, + ) -> ContendedResult { + let (shard, hash) = self.shard_for(&key).unwrap(); + + shard + .try_write() + .map(|mut guard| { + let mut lcs = self.lifecycle.begin_request(); + let result = guard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); + // result cannot err with the Insert strategy + debug_assert!(result.is_ok()); + ContendedResult::Ok(lcs) + }) + .unwrap_or(ContendedResult::Contended) + } + /// Clear all items from the cache pub fn clear(&self) { for s in self.shards.iter() { @@ -1499,4 +1623,133 @@ mod tests { } } } + + // --- Non-blocking method tests --- + + #[test] + fn test_contended_result_helpers() { + let ok: ContendedResult = ContendedResult::Ok(42); + assert!(!ok.is_contended()); + assert_eq!(ok.ok(), Some(42)); + + let contended: ContendedResult = ContendedResult::Contended; + assert!(contended.is_contended()); + assert_eq!(contended.ok(), None); + } + + #[test] + fn test_try_contains_key() { + let cache = Cache::new(100); + cache.insert(1, 10); + + assert_eq!(cache.try_contains_key(&1), ContendedResult::Ok(true)); + assert_eq!(cache.try_contains_key(&2), ContendedResult::Ok(false)); + } + + #[test] + fn test_try_contains_key_contended() { + let cache = Cache::new(100); + cache.insert(1, 10); + // Hold write locks on all shards so try_read is blocked. + let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); + assert_eq!(cache.try_contains_key(&1), ContendedResult::Contended); + } + + #[test] + fn test_try_get() { + let cache = Cache::new(100); + cache.insert(1, 10); + + assert_eq!(cache.try_get(&1), ContendedResult::Ok(Some(10))); + assert_eq!(cache.try_get(&2), ContendedResult::Ok(None)); + } + + #[test] + fn test_try_get_contended() { + let cache = Cache::new(100); + cache.insert(1, 10); + let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); + assert_eq!(cache.try_get(&1), ContendedResult::Contended); + } + + #[test] + fn test_try_peek() { + let cache = Cache::new(100); + cache.insert(1, 10); + + assert_eq!(cache.try_peek(&1), ContendedResult::Ok(Some(10))); + assert_eq!(cache.try_peek(&2), ContendedResult::Ok(None)); + } + + #[test] + fn test_try_peek_contended() { + let cache = Cache::new(100); + cache.insert(1, 10); + let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); + assert_eq!(cache.try_peek(&1), ContendedResult::Contended); + } + + #[test] + fn test_try_remove() { + let cache = Cache::new(100); + cache.insert(1, 10); + + assert_eq!(cache.try_remove(&1), ContendedResult::Ok(Some((1, 10)))); + assert_eq!(cache.try_remove(&1), ContendedResult::Ok(None)); + assert_eq!(cache.try_remove(&99), ContendedResult::Ok(None)); + } + + #[test] + fn test_try_remove_contended() { + let cache = Cache::new(100); + cache.insert(1, 10); + // Hold read locks on all shards so try_write is blocked. + let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); + assert_eq!(cache.try_remove(&1), ContendedResult::Contended); + drop(guards); + // Item must still be present since the remove did not happen. + assert_eq!(cache.get(&1), Some(10)); + } + + #[test] + fn test_try_insert() { + let cache = Cache::new(100); + + assert_eq!(cache.try_insert(1, 10), ContendedResult::Ok(())); + assert_eq!(cache.get(&1), Some(10)); + + // Insert same key overwrites the previous value. + assert_eq!(cache.try_insert(1, 20), ContendedResult::Ok(())); + assert_eq!(cache.get(&1), Some(20)); + } + + #[test] + fn test_try_insert_contended() { + let cache = Cache::new(100); + let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); + assert_eq!(cache.try_insert(1, 10), ContendedResult::Contended); + drop(guards); + assert_eq!(cache.get(&1), None); + } + + #[test] + fn test_try_insert_with_lifecycle() { + let cache = Cache::new(100); + + // Successful insert returns the lifecycle request state. + let result = cache.try_insert_with_lifecycle(1, 10); + assert!(!result.is_contended()); + let lcs = result.ok().unwrap(); + cache.lifecycle.end_request(lcs); + assert_eq!(cache.get(&1), Some(10)); + + // Contended when a read lock is held. + let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); + assert_eq!( + cache.try_insert_with_lifecycle(2, 20), + ContendedResult::Contended + ); + drop(guards); + assert_eq!(cache.get(&2), None); + } } From 1b13f573e24793b5563b59ffc4be520679a9f40a Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla <105630300+fsdvh@users.noreply.github.com> Date: Mon, 13 Apr 2026 10:40:23 +0200 Subject: [PATCH 12/23] Expose shard_index method (#2) Expose shard_index method --- src/sync.rs | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index e9f6f29..0057c64 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -231,6 +231,37 @@ impl< self.shards.iter().map(|s| s.read().hits()).sum() } + #[inline] + fn compute_shard_index(&self, hash: u64) -> u64 { + // Give preference to the bits in the middle of the hash. When choosing the + // shard, rotate the hash by usize::BITS / 2 so we avoid the lower bits and + // the highest 7 bits that hashbrown uses internally for probing, improving + // the real entropy available to each hashbrown shard. + hash.rotate_right(usize::BITS / 2) & self.shards_mask + } + + /// Returns the shard index for the given key. + /// + /// The returned index is guaranteed to be in `[0, num_shards())`. + /// + /// # Use cases + /// + /// - **Batching**: group keys by shard index before acquiring shard locks, so + /// each lock is taken only once per batch instead of once per key. + /// + /// # Notes + /// + /// The mapping from key to shard index depends on the [`BuildHasher`] supplied + /// at construction time. If two `Cache` instances are built with different + /// hashers, the same key may map to different shard indices. + /// + /// [`BuildHasher`]: std::hash::BuildHasher + #[inline] + pub fn shard_index + ?Sized>(&self, key: &Q) -> usize { + let hash = self.hash_builder.hash_one(key); + self.compute_shard_index(hash) as usize + } + #[inline] fn shard_for( &self, @@ -243,11 +274,7 @@ impl< Q: Hash + Equivalent + ?Sized, { let hash = self.hash_builder.hash_one(key); - // When choosing the shard, rotate the hash bits usize::BITS / 2 so that we - // give preference to the bits in the middle of the hash. - // Internally hashbrown uses the lower bits for start of probing + the 7 highest, - // so by picking something else we improve the real entropy available to each hashbrown shard. - let shard_idx = (hash.rotate_right(usize::BITS / 2) & self.shards_mask) as usize; + let shard_idx = self.compute_shard_index(hash) as usize; self.shards.get(shard_idx).map(|s| (s, hash)) } From f99d3e964cab726f508102101089a28c40d7ca24 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla <105630300+fsdvh@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:57:53 +0200 Subject: [PATCH 13/23] Update src/sync.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/sync.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index e9f6f29..e7c7220 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -428,15 +428,20 @@ impl< } /// Attempts to insert an item in the cache with key `key`. - /// Returns [`ContendedResult::Ok`] if the item was inserted, or [`ContendedResult::Contended`] if the shard lock was contended. - pub fn try_insert(&self, key: Key, value: Val) -> ContendedResult<()> { - match self.try_insert_with_lifecycle(key, value) { - ContendedResult::Ok(lcs) => { - self.lifecycle.end_request(lcs); - ContendedResult::Ok(()) - } - ContendedResult::Contended => ContendedResult::Contended, - } + /// Returns `Ok(())` if the item was inserted, or `Err((key, value))` if the shard lock + /// was contended before the insert could be performed. + pub fn try_insert(&self, key: Key, value: Val) -> Result<(), (Key, Val)> { + let (shard, hash) = self.shard_for(&key).unwrap(); + let Some(mut shard) = shard.try_write() else { + return Err((key, value)); + }; + + let mut lcs = self.lifecycle.begin_request(); + let result = shard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); + // result cannot err with the Insert strategy + debug_assert!(result.is_ok()); + self.lifecycle.end_request(lcs); + Ok(()) } /// Inserts an item in the cache with key `key`. From 44a052ae712679c6e824ecf0a1864ac0c6297b86 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Sun, 19 Apr 2026 01:45:07 +0200 Subject: [PATCH 14/23] Simplify signature --- src/sync.rs | 185 +++++++++++++++++++++------------------------------- 1 file changed, 75 insertions(+), 110 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index e7c7220..3135709 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -18,29 +18,6 @@ use crate::shard::EntryOrPlaceholder; pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard}; use crate::sync_placeholder::{JoinFuture, JoinResult}; -/// The result of a non-blocking cache operation. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ContendedResult { - /// The operation succeeded. For read operations the inner value holds the lookup result; - /// for write operations it holds the lifecycle request state (or `()` for [`Cache::try_insert`]). - Ok(Val), - /// The shard lock could not be acquired without blocking. The operation was not performed. - Contended, -} - -impl ContendedResult { - pub fn ok(self) -> Option { - match self { - ContendedResult::Ok(val) => Some(val), - ContendedResult::Contended => None, - } - } - - pub fn is_contended(&self) -> bool { - matches!(self, ContendedResult::Contended) - } -} - /// A concurrent cache /// /// The concurrent cache is internally composed of equally sized shards, each of which is independently @@ -271,20 +248,20 @@ impl< } /// Attempts to check if a key exists in the cache without blocking. - /// Returns [`ContendedResult::Ok(true)`] if present, [`ContendedResult::Ok(false)`] if absent, - /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. - pub fn try_contains_key(&self, key: &Q) -> ContendedResult + /// Returns `Ok(true)` if present, `Ok(false)` if absent, + /// or `Err(())` if the shard lock could not be acquired without blocking. + pub fn try_contains_key(&self, key: &Q) -> Result where Q: Hash + Equivalent + ?Sized, { let Some((shard, hash)) = self.shard_for(key) else { - return ContendedResult::Ok(false); + return Ok(false); }; - shard - .try_read() - .map(|guard| ContendedResult::Ok(guard.contains(hash, key))) - .unwrap_or(ContendedResult::Contended) + match shard.try_read() { + Some(guard) => Ok(guard.contains(hash, key)), + None => Err(()), + } } /// Fetches an item from the cache whose key is `key`. @@ -297,19 +274,20 @@ impl< } /// Attempts to fetch an item from the cache whose key is `key`. - /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, - /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. - pub fn try_get(&self, key: &Q) -> ContendedResult> + /// Returns `Ok(Some(val))` if the key is present, `Ok(None)` if absent, + /// or `Err(())` if the shard lock could not be acquired without blocking. + pub fn try_get(&self, key: &Q) -> Result, ()> where Q: Hash + Equivalent + ?Sized, { let Some((shard, hash)) = self.shard_for(key) else { - return ContendedResult::Ok(None); + return Ok(None); }; - shard - .try_read() - .map(|guard| ContendedResult::Ok(guard.get(hash, key).cloned())) - .unwrap_or(ContendedResult::Contended) + + match shard.try_read() { + Some(guard) => Ok(guard.get(hash, key).cloned()), + None => Err(()), + } } /// Peeks an item from the cache whose key is `key`. @@ -324,19 +302,19 @@ impl< /// Attempts to peek an item from the cache whose key is `key`. /// Contrary to gets, peeks don't alter the key "hotness". - /// Returns [`ContendedResult::Ok(Some(val))`] if the key is present, [`ContendedResult::Ok(None)`] if absent, - /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. - pub fn try_peek(&self, key: &Q) -> ContendedResult> + /// Returns `Ok(Some(val))` if the key is present, `Ok(None)` if absent, + /// or `Err(())` if the shard lock could not be acquired without blocking. + pub fn try_peek(&self, key: &Q) -> Result, ()> where Q: Hash + Equivalent + ?Sized, { let Some((shard, hash)) = self.shard_for(key) else { - return ContendedResult::Ok(None); + return Ok(None); }; - shard - .try_read() - .map(|guard| ContendedResult::Ok(guard.peek(hash, key).cloned())) - .unwrap_or(ContendedResult::Contended) + match shard.try_read() { + Some(guard) => Ok(guard.peek(hash, key).cloned()), + None => Err(()), + } } /// Remove an item from the cache whose key is `key`. @@ -350,20 +328,20 @@ impl< } /// Attempts to remove an item from the cache whose key is `key`. - /// Returns [`ContendedResult::Ok(Some(entry))`] with the removed entry if present, [`ContendedResult::Ok(None)`] if absent, - /// or [`ContendedResult::Contended`] if the shard lock could not be acquired without blocking. - pub fn try_remove(&self, key: &Q) -> ContendedResult> + /// Returns `Ok(Some(entry))` with the removed entry if present, `Ok(None)` if absent, + /// or `Err(())` if the shard lock could not be acquired without blocking. + pub fn try_remove(&self, key: &Q) -> Result, ()> where Q: Hash + Equivalent + ?Sized, { let Some((shard, hash)) = self.shard_for(key) else { - return ContendedResult::Ok(None); + return Ok(None); }; - shard - .try_write() - .map(|mut guard| ContendedResult::Ok(guard.remove(hash, key))) - .unwrap_or(ContendedResult::Contended) + match shard.try_write() { + Some(mut guard) => Ok(guard.remove(hash, key)), + None => Err(()), + } } /// Remove an item from the cache whose key is `key` if `f(&value)` returns `true` for that entry. @@ -427,21 +405,23 @@ impl< self.lifecycle.end_request(lcs); } - /// Attempts to insert an item in the cache with key `key`. + /// Attempts to insert an item in the cache with key `key` without blocking. /// Returns `Ok(())` if the item was inserted, or `Err((key, value))` if the shard lock - /// was contended before the insert could be performed. + /// could not be acquired without blocking. pub fn try_insert(&self, key: Key, value: Val) -> Result<(), (Key, Val)> { let (shard, hash) = self.shard_for(&key).unwrap(); - let Some(mut shard) = shard.try_write() else { - return Err((key, value)); - }; - let mut lcs = self.lifecycle.begin_request(); - let result = shard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); - // result cannot err with the Insert strategy - debug_assert!(result.is_ok()); - self.lifecycle.end_request(lcs); - Ok(()) + match shard.try_write() { + Some(mut shard) => { + let mut lcs = self.lifecycle.begin_request(); + let result = shard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); + // result cannot err with the Insert strategy + debug_assert!(result.is_ok()); + self.lifecycle.end_request(lcs); + Ok(()) + } + _ => Err((key, value)), + } } /// Inserts an item in the cache with key `key`. @@ -456,26 +436,26 @@ impl< lcs } - /// Attempts to insert an item in the cache with key `key`. - /// Returns [`ContendedResult::Ok`] with the lifecycle request state if the item was inserted, - /// or [`ContendedResult::Contended`] if the shard lock was contended. + /// Attempts to insert an item in the cache with key `key` without blocking. + /// Returns `Ok(lcs)` with the lifecycle request state if the item was inserted, + /// or `Err((key, value))` if the shard lock could not be acquired without blocking. pub fn try_insert_with_lifecycle( &self, key: Key, value: Val, - ) -> ContendedResult { + ) -> Result { let (shard, hash) = self.shard_for(&key).unwrap(); - shard - .try_write() - .map(|mut guard| { + match shard.try_write() { + Some(mut shard) => { let mut lcs = self.lifecycle.begin_request(); - let result = guard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); + let result = shard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); // result cannot err with the Insert strategy debug_assert!(result.is_ok()); - ContendedResult::Ok(lcs) - }) - .unwrap_or(ContendedResult::Contended) + Ok(lcs) + } + _ => Err((key, value)), + } } /// Clear all items from the cache @@ -1630,25 +1610,13 @@ mod tests { } // --- Non-blocking method tests --- - - #[test] - fn test_contended_result_helpers() { - let ok: ContendedResult = ContendedResult::Ok(42); - assert!(!ok.is_contended()); - assert_eq!(ok.ok(), Some(42)); - - let contended: ContendedResult = ContendedResult::Contended; - assert!(contended.is_contended()); - assert_eq!(contended.ok(), None); - } - #[test] fn test_try_contains_key() { let cache = Cache::new(100); cache.insert(1, 10); - assert_eq!(cache.try_contains_key(&1), ContendedResult::Ok(true)); - assert_eq!(cache.try_contains_key(&2), ContendedResult::Ok(false)); + assert_eq!(cache.try_contains_key(&1), Ok(true)); + assert_eq!(cache.try_contains_key(&2), Ok(false)); } #[test] @@ -1657,7 +1625,7 @@ mod tests { cache.insert(1, 10); // Hold write locks on all shards so try_read is blocked. let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); - assert_eq!(cache.try_contains_key(&1), ContendedResult::Contended); + assert_eq!(cache.try_contains_key(&1), Err(())); } #[test] @@ -1665,8 +1633,8 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert_eq!(cache.try_get(&1), ContendedResult::Ok(Some(10))); - assert_eq!(cache.try_get(&2), ContendedResult::Ok(None)); + assert_eq!(cache.try_get(&1), Ok(Some(10))); + assert_eq!(cache.try_get(&2), Ok(None)); } #[test] @@ -1674,7 +1642,7 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); - assert_eq!(cache.try_get(&1), ContendedResult::Contended); + assert_eq!(cache.try_get(&1), Err(())); } #[test] @@ -1682,8 +1650,8 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert_eq!(cache.try_peek(&1), ContendedResult::Ok(Some(10))); - assert_eq!(cache.try_peek(&2), ContendedResult::Ok(None)); + assert_eq!(cache.try_peek(&1), Ok(Some(10))); + assert_eq!(cache.try_peek(&2), Ok(None)); } #[test] @@ -1691,7 +1659,7 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); - assert_eq!(cache.try_peek(&1), ContendedResult::Contended); + assert_eq!(cache.try_peek(&1), Err(())); } #[test] @@ -1699,9 +1667,9 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert_eq!(cache.try_remove(&1), ContendedResult::Ok(Some((1, 10)))); - assert_eq!(cache.try_remove(&1), ContendedResult::Ok(None)); - assert_eq!(cache.try_remove(&99), ContendedResult::Ok(None)); + assert_eq!(cache.try_remove(&1), Ok(Some((1, 10)))); + assert_eq!(cache.try_remove(&1), Ok(None)); + assert_eq!(cache.try_remove(&99), Ok(None)); } #[test] @@ -1710,7 +1678,7 @@ mod tests { cache.insert(1, 10); // Hold read locks on all shards so try_write is blocked. let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); - assert_eq!(cache.try_remove(&1), ContendedResult::Contended); + assert_eq!(cache.try_remove(&1), Err(())); drop(guards); // Item must still be present since the remove did not happen. assert_eq!(cache.get(&1), Some(10)); @@ -1720,11 +1688,11 @@ mod tests { fn test_try_insert() { let cache = Cache::new(100); - assert_eq!(cache.try_insert(1, 10), ContendedResult::Ok(())); + assert_eq!(cache.try_insert(1, 10), Ok(())); assert_eq!(cache.get(&1), Some(10)); // Insert same key overwrites the previous value. - assert_eq!(cache.try_insert(1, 20), ContendedResult::Ok(())); + assert_eq!(cache.try_insert(1, 20), Ok(())); assert_eq!(cache.get(&1), Some(20)); } @@ -1732,7 +1700,7 @@ mod tests { fn test_try_insert_contended() { let cache = Cache::new(100); let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); - assert_eq!(cache.try_insert(1, 10), ContendedResult::Contended); + assert_eq!(cache.try_insert(1, 10), Err((1, 10))); drop(guards); assert_eq!(cache.get(&1), None); } @@ -1743,17 +1711,14 @@ mod tests { // Successful insert returns the lifecycle request state. let result = cache.try_insert_with_lifecycle(1, 10); - assert!(!result.is_contended()); + assert!(result.is_ok()); let lcs = result.ok().unwrap(); cache.lifecycle.end_request(lcs); assert_eq!(cache.get(&1), Some(10)); // Contended when a read lock is held. let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); - assert_eq!( - cache.try_insert_with_lifecycle(2, 20), - ContendedResult::Contended - ); + assert_eq!(cache.try_insert_with_lifecycle(2, 20), Err((2, 20))); drop(guards); assert_eq!(cache.get(&2), None); } From 69b7c64211adb8a296811af7cc7080174d05159b Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Mon, 20 Apr 2026 13:18:41 +0200 Subject: [PATCH 15/23] Simplify --- src/sync.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index 3135709..a5f7423 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -409,19 +409,9 @@ impl< /// Returns `Ok(())` if the item was inserted, or `Err((key, value))` if the shard lock /// could not be acquired without blocking. pub fn try_insert(&self, key: Key, value: Val) -> Result<(), (Key, Val)> { - let (shard, hash) = self.shard_for(&key).unwrap(); - - match shard.try_write() { - Some(mut shard) => { - let mut lcs = self.lifecycle.begin_request(); - let result = shard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); - // result cannot err with the Insert strategy - debug_assert!(result.is_ok()); - self.lifecycle.end_request(lcs); - Ok(()) - } - _ => Err((key, value)), - } + let lcs = self.try_insert_with_lifecycle(key, value)?; + self.lifecycle.end_request(lcs); + Ok(()) } /// Inserts an item in the cache with key `key`. From b4012a6d8b980d84089dde2843609eba86b536c0 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Mon, 20 Apr 2026 13:41:25 +0200 Subject: [PATCH 16/23] clippy --- src/sync.rs | 53 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index a5f7423..6c44dbf 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -18,6 +18,17 @@ use crate::shard::EntryOrPlaceholder; pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard}; use crate::sync_placeholder::{JoinFuture, JoinResult}; +#[derive(Debug)] +pub struct LockContention; + +impl std::fmt::Display for LockContention { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Lock Contention") + } +} + +impl std::error::Error for LockContention {} + /// A concurrent cache /// /// The concurrent cache is internally composed of equally sized shards, each of which is independently @@ -250,7 +261,7 @@ impl< /// Attempts to check if a key exists in the cache without blocking. /// Returns `Ok(true)` if present, `Ok(false)` if absent, /// or `Err(())` if the shard lock could not be acquired without blocking. - pub fn try_contains_key(&self, key: &Q) -> Result + pub fn try_contains_key(&self, key: &Q) -> Result where Q: Hash + Equivalent + ?Sized, { @@ -260,7 +271,7 @@ impl< match shard.try_read() { Some(guard) => Ok(guard.contains(hash, key)), - None => Err(()), + None => Err(LockContention), } } @@ -276,7 +287,7 @@ impl< /// Attempts to fetch an item from the cache whose key is `key`. /// Returns `Ok(Some(val))` if the key is present, `Ok(None)` if absent, /// or `Err(())` if the shard lock could not be acquired without blocking. - pub fn try_get(&self, key: &Q) -> Result, ()> + pub fn try_get(&self, key: &Q) -> Result, LockContention> where Q: Hash + Equivalent + ?Sized, { @@ -286,7 +297,7 @@ impl< match shard.try_read() { Some(guard) => Ok(guard.get(hash, key).cloned()), - None => Err(()), + None => Err(LockContention), } } @@ -304,7 +315,7 @@ impl< /// Contrary to gets, peeks don't alter the key "hotness". /// Returns `Ok(Some(val))` if the key is present, `Ok(None)` if absent, /// or `Err(())` if the shard lock could not be acquired without blocking. - pub fn try_peek(&self, key: &Q) -> Result, ()> + pub fn try_peek(&self, key: &Q) -> Result, LockContention> where Q: Hash + Equivalent + ?Sized, { @@ -313,7 +324,7 @@ impl< }; match shard.try_read() { Some(guard) => Ok(guard.peek(hash, key).cloned()), - None => Err(()), + None => Err(LockContention), } } @@ -330,7 +341,7 @@ impl< /// Attempts to remove an item from the cache whose key is `key`. /// Returns `Ok(Some(entry))` with the removed entry if present, `Ok(None)` if absent, /// or `Err(())` if the shard lock could not be acquired without blocking. - pub fn try_remove(&self, key: &Q) -> Result, ()> + pub fn try_remove(&self, key: &Q) -> Result, LockContention> where Q: Hash + Equivalent + ?Sized, { @@ -340,7 +351,7 @@ impl< match shard.try_write() { Some(mut guard) => Ok(guard.remove(hash, key)), - None => Err(()), + None => Err(LockContention), } } @@ -1605,8 +1616,8 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert_eq!(cache.try_contains_key(&1), Ok(true)); - assert_eq!(cache.try_contains_key(&2), Ok(false)); + assert!(cache.try_contains_key(&1).is_ok_and(|v| v)); + assert!(cache.try_contains_key(&2).is_ok_and(|v| !v)); } #[test] @@ -1615,7 +1626,7 @@ mod tests { cache.insert(1, 10); // Hold write locks on all shards so try_read is blocked. let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); - assert_eq!(cache.try_contains_key(&1), Err(())); + assert!(cache.try_contains_key(&1).is_err()); } #[test] @@ -1623,8 +1634,8 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert_eq!(cache.try_get(&1), Ok(Some(10))); - assert_eq!(cache.try_get(&2), Ok(None)); + assert!(cache.try_get(&1).is_ok_and(|v| v == Some(10))); + assert!(cache.try_get(&2).is_ok_and(|v| v.is_none())); } #[test] @@ -1632,7 +1643,7 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); - assert_eq!(cache.try_get(&1), Err(())); + assert!(cache.try_get(&1).is_err()); } #[test] @@ -1640,8 +1651,8 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert_eq!(cache.try_peek(&1), Ok(Some(10))); - assert_eq!(cache.try_peek(&2), Ok(None)); + assert!(cache.try_peek(&1).is_ok_and(|v| v == Some(10))); + assert!(cache.try_peek(&2).is_ok_and(|v| v.is_none())); } #[test] @@ -1649,7 +1660,7 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); let _guards: Vec<_> = cache.shards.iter().map(|s| s.write()).collect(); - assert_eq!(cache.try_peek(&1), Err(())); + assert!(cache.try_peek(&1).is_err()); } #[test] @@ -1657,9 +1668,9 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert_eq!(cache.try_remove(&1), Ok(Some((1, 10)))); - assert_eq!(cache.try_remove(&1), Ok(None)); - assert_eq!(cache.try_remove(&99), Ok(None)); + assert!(cache.try_remove(&1).is_ok_and(|v| v == Some((1, 10)))); + assert!(cache.try_remove(&1).is_ok_and(|v| v == None)); + assert!(cache.try_remove(&99).is_ok_and(|v| v == None)); } #[test] @@ -1668,7 +1679,7 @@ mod tests { cache.insert(1, 10); // Hold read locks on all shards so try_write is blocked. let guards: Vec<_> = cache.shards.iter().map(|s| s.read()).collect(); - assert_eq!(cache.try_remove(&1), Err(())); + assert!(cache.try_remove(&1).is_err()); drop(guards); // Item must still be present since the remove did not happen. assert_eq!(cache.get(&1), Some(10)); From 5bd558dfc28dc6667406b013541afb4cd8a2157b Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Mon, 20 Apr 2026 13:43:23 +0200 Subject: [PATCH 17/23] More docs --- README.md | 1 + src/sync.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8c986e8..6c24f09 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Lightweight and high performance concurrent cache optimized for low cache overhe * Scales well with the number of threads * Atomic operations with `get_or_insert` and `get_value_or_guard` functions * Atomic async operations with `get_or_insert_async` and `get_value_or_guard_async` functions +* Non-blocking `try_get`, `try_insert`, `try_remove`, and related methods that return `Err(LockContention)` instead of blocking * Closure-based `entry` API for atomic inspect-and-act patterns (keep, remove, replace) * Supports item pinning * Iteration and draining diff --git a/src/sync.rs b/src/sync.rs index 6c44dbf..c75a6b0 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -18,6 +18,8 @@ use crate::shard::EntryOrPlaceholder; pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard}; use crate::sync_placeholder::{JoinFuture, JoinResult}; +/// Error returned by non-blocking cache operations when the relevant shard lock +/// could not be acquired immediately. #[derive(Debug)] pub struct LockContention; @@ -260,7 +262,7 @@ impl< /// Attempts to check if a key exists in the cache without blocking. /// Returns `Ok(true)` if present, `Ok(false)` if absent, - /// or `Err(())` if the shard lock could not be acquired without blocking. + /// or `Err(LockContention)` if the shard lock could not be acquired without blocking. pub fn try_contains_key(&self, key: &Q) -> Result where Q: Hash + Equivalent + ?Sized, @@ -286,7 +288,7 @@ impl< /// Attempts to fetch an item from the cache whose key is `key`. /// Returns `Ok(Some(val))` if the key is present, `Ok(None)` if absent, - /// or `Err(())` if the shard lock could not be acquired without blocking. + /// or `Err(LockContention)` if the shard lock could not be acquired without blocking. pub fn try_get(&self, key: &Q) -> Result, LockContention> where Q: Hash + Equivalent + ?Sized, @@ -314,7 +316,7 @@ impl< /// Attempts to peek an item from the cache whose key is `key`. /// Contrary to gets, peeks don't alter the key "hotness". /// Returns `Ok(Some(val))` if the key is present, `Ok(None)` if absent, - /// or `Err(())` if the shard lock could not be acquired without blocking. + /// or `Err(LockContention)` if the shard lock could not be acquired without blocking. pub fn try_peek(&self, key: &Q) -> Result, LockContention> where Q: Hash + Equivalent + ?Sized, @@ -340,7 +342,7 @@ impl< /// Attempts to remove an item from the cache whose key is `key`. /// Returns `Ok(Some(entry))` with the removed entry if present, `Ok(None)` if absent, - /// or `Err(())` if the shard lock could not be acquired without blocking. + /// or `Err(LockContention)` if the shard lock could not be acquired without blocking. pub fn try_remove(&self, key: &Q) -> Result, LockContention> where Q: Hash + Equivalent + ?Sized, From 3b6bc316298d42bccb1dd7303073375dc8cd317a Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla <105630300+fsdvh@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:16:13 +0200 Subject: [PATCH 18/23] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c24f09..046d03c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Lightweight and high performance concurrent cache optimized for low cache overhe * Scales well with the number of threads * Atomic operations with `get_or_insert` and `get_value_or_guard` functions * Atomic async operations with `get_or_insert_async` and `get_value_or_guard_async` functions -* Non-blocking `try_get`, `try_insert`, `try_remove`, and related methods that return `Err(LockContention)` instead of blocking +* Non-blocking `try_get`, `try_insert`, `try_remove`, and related methods that return an error instead of blocking: typically `Err(LockContention)`, or `Err((Key, Val))` for `try_insert`/`try_insert_with_lifecycle` so inputs are preserved * Closure-based `entry` API for atomic inspect-and-act patterns (keep, remove, replace) * Supports item pinning * Iteration and draining From ff7cfad2268f9dd9270352117ce3ef1283490a70 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla <105630300+fsdvh@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:16:49 +0200 Subject: [PATCH 19/23] Update src/sync.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/sync.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index c75a6b0..f015b7b 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -1670,9 +1670,9 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert!(cache.try_remove(&1).is_ok_and(|v| v == Some((1, 10)))); - assert!(cache.try_remove(&1).is_ok_and(|v| v == None)); - assert!(cache.try_remove(&99).is_ok_and(|v| v == None)); + assert!(cache.try_remove(&1).is_ok_and(|v| matches!(v, Some((1, 10))))); + assert!(cache.try_remove(&1).is_ok_and(|v| v.is_none())); + assert!(cache.try_remove(&99).is_ok_and(|v| v.is_none())); } #[test] From a85cfb82ad129d46d7c5237ae1d1ea96b66c4453 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Mon, 20 Apr 2026 15:19:26 +0200 Subject: [PATCH 20/23] AI suggestions --- src/sync.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index f015b7b..a9ffa5b 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -1636,7 +1636,7 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert!(cache.try_get(&1).is_ok_and(|v| v == Some(10))); + assert!(cache.try_get(&1).is_ok_and(|v| matches!(v, Some(10)))); assert!(cache.try_get(&2).is_ok_and(|v| v.is_none())); } @@ -1653,7 +1653,7 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert!(cache.try_peek(&1).is_ok_and(|v| v == Some(10))); + assert!(cache.try_peek(&1).is_ok_and(|v| matches!(v, Some(10)))); assert!(cache.try_peek(&2).is_ok_and(|v| v.is_none())); } @@ -1670,7 +1670,9 @@ mod tests { let cache = Cache::new(100); cache.insert(1, 10); - assert!(cache.try_remove(&1).is_ok_and(|v| matches!(v, Some((1, 10))))); + assert!(cache + .try_remove(&1) + .is_ok_and(|v| matches!(v, Some((1, 10))))); assert!(cache.try_remove(&1).is_ok_and(|v| v.is_none())); assert!(cache.try_remove(&99).is_ok_and(|v| v.is_none())); } From 8eb667ce07cef1ee9a193a80bc6f9065c8b155f8 Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla <105630300+fsdvh@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:34:24 +0200 Subject: [PATCH 21/23] Update src/sync.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/sync.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sync.rs b/src/sync.rs index a9ffa5b..80ee68f 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -18,8 +18,11 @@ use crate::shard::EntryOrPlaceholder; pub use crate::sync_placeholder::{EntryAction, EntryResult, GuardResult, PlaceholderGuard}; use crate::sync_placeholder::{JoinFuture, JoinResult}; -/// Error returned by non-blocking cache operations when the relevant shard lock -/// could not be acquired immediately. +/// Error returned by non-blocking cache operations that do not consume their +/// inputs when the relevant shard lock could not be acquired immediately. +/// +/// This is used by borrowed-key/read-path operations. Non-blocking operations +/// that consume owned inputs may instead return those inputs on contention. #[derive(Debug)] pub struct LockContention; From cb6449acb23a082b102157bfc2217725748859f5 Mon Sep 17 00:00:00 2001 From: Arthur Silva Date: Tue, 28 Apr 2026 00:43:46 +0200 Subject: [PATCH 22/23] review --- README.md | 2 +- src/sync.rs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 046d03c..4174151 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Lightweight and high performance concurrent cache optimized for low cache overhe * Scales well with the number of threads * Atomic operations with `get_or_insert` and `get_value_or_guard` functions * Atomic async operations with `get_or_insert_async` and `get_value_or_guard_async` functions -* Non-blocking `try_get`, `try_insert`, `try_remove`, and related methods that return an error instead of blocking: typically `Err(LockContention)`, or `Err((Key, Val))` for `try_insert`/`try_insert_with_lifecycle` so inputs are preserved +* Non-blocking methods that return immediately on lock contention. * Closure-based `entry` API for atomic inspect-and-act patterns (keep, remove, replace) * Supports item pinning * Iteration and draining diff --git a/src/sync.rs b/src/sync.rs index 7f6f5f3..ee3bb71 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -22,8 +22,9 @@ use crate::sync_placeholder::{JoinFuture, JoinResult}; /// inputs when the relevant shard lock could not be acquired immediately. /// /// This is used by borrowed-key/read-path operations. Non-blocking operations -/// that consume owned inputs may instead return those inputs on contention. -#[derive(Debug)] +/// that consume owned inputs (e.g. `try_insert`) instead return those inputs +/// on contention so the caller can retry or discard without losing data. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct LockContention; impl std::fmt::Display for LockContention { @@ -450,7 +451,8 @@ impl< /// Attempts to insert an item in the cache with key `key` without blocking. /// Returns `Ok(())` if the item was inserted, or `Err((key, value))` if the shard lock - /// could not be acquired without blocking. + /// could not be acquired without blocking. Lock contention is the only failure + /// mode: the inputs are returned so the caller can retry or discard them. pub fn try_insert(&self, key: Key, value: Val) -> Result<(), (Key, Val)> { let lcs = self.try_insert_with_lifecycle(key, value)?; self.lifecycle.end_request(lcs); @@ -472,6 +474,8 @@ impl< /// Attempts to insert an item in the cache with key `key` without blocking. /// Returns `Ok(lcs)` with the lifecycle request state if the item was inserted, /// or `Err((key, value))` if the shard lock could not be acquired without blocking. + /// Lock contention is the only failure mode: the inputs are returned so the + /// caller can retry or discard them. pub fn try_insert_with_lifecycle( &self, key: Key, From f0e2fe0f1039b2e25aae0b7233b2701ef63a5a0a Mon Sep 17 00:00:00 2001 From: Faiaz Sanaulla Date: Wed, 29 Apr 2026 10:11:39 +0200 Subject: [PATCH 23/23] Move begin_request otside of lock --- src/sync.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sync.rs b/src/sync.rs index 7f6f5f3..31efa15 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -477,11 +477,13 @@ impl< key: Key, value: Val, ) -> Result { + // Tradeoff: begin_request is called before acquiring the shard lock to avoid holding + // the lock during potentially expensive lifecycle initialization. + let mut lcs = self.lifecycle.begin_request(); let (shard, hash) = self.shard_for(&key).unwrap(); match shard.try_write() { Some(mut shard) => { - let mut lcs = self.lifecycle.begin_request(); let result = shard.insert(&mut lcs, hash, key, value, InsertStrategy::Insert); // result cannot err with the Insert strategy debug_assert!(result.is_ok());