Skip to content

Add non-blocking cache methods (non-blocking feature)#112

Open
fsdvh wants to merge 9 commits intoarthurprs:masterfrom
fsdvh:non-blocking-methods
Open

Add non-blocking cache methods (non-blocking feature)#112
fsdvh wants to merge 9 commits intoarthurprs:masterfrom
fsdvh:non-blocking-methods

Conversation

@fsdvh
Copy link
Copy Markdown

@fsdvh fsdvh commented Apr 12, 2026

Adds opt-in, non-blocking variants of the core cache operations via a new non-blocking Cargo feature.

Motivation

Under high write contention a caller may prefer to skip a cache operation rather than block waiting for a shard lock. The existing API offers no way to express this — every operation blocks until the lock is acquired.

Changes

Cargo.toml

  • New non-blocking = [] feature flag (off by default).

src/rw_lock.rs

  • Added try_read / try_write on the internal RwLock wrapper, gated on #[cfg(feature = "non-blocking")].
  • Normalises the return type to Option<Guard> across all three backing implementations (parking_lot, crossbeam ShardedLock, std::sync).

src/sync.rs

  • New ContendedResult<Val> enum with variants Ok(Val) and Contended, plus ok() / is_contended() helpers.
  • Six new methods on Cache, each returning ContendedResult and falling back to Contended instead of blocking:
    • try_contains_key
    • try_get
    • try_peek
    • try_remove
    • try_insert
    • try_insert_with_lifecycle

Usage

quick_cache = { version = "...", features = ["non-blocking"] }
match cache.try_get(&key) {
    ContendedResult::Ok(Some(val)) => { /* cache hit */ }
    ContendedResult::Ok(None)      => { /* cache miss */ }
    ContendedResult::Contended     => { /* shard was locked, skip or fall back */ }
}

@fsdvh fsdvh marked this pull request as ready for review April 12, 2026 10:21
@fsdvh
Copy link
Copy Markdown
Author

fsdvh commented Apr 12, 2026

Let me know what you think, @arthurprs. Thank you in advance!

@fsdvh fsdvh changed the title Add non-blocking methods for sync cache Add non-blocking cache methods (non-blocking feature) Apr 12, 2026
@arthurprs arthurprs requested a review from Copilot April 12, 2026 17:00
@arthurprs
Copy link
Copy Markdown
Owner

Thanks for the PR. In general I like the additions 👍 Do you think there's a practical benefit putting them behind the feature flag?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in non-blocking Cargo feature that exposes non-blocking cache operations, allowing callers to skip work when a shard lock is contended instead of blocking. This extends the existing synchronous cache API with contention-aware variants while keeping the default (blocking) behavior unchanged.

Changes:

  • Introduces a non-blocking feature flag in Cargo.toml.
  • Adds try_read / try_write to the internal RwLock wrapper (feature-gated).
  • Adds ContendedResult plus Cache::try_* methods that return Contended on lock contention rather than blocking.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
Cargo.toml Adds the non-blocking feature flag.
src/rw_lock.rs Implements non-blocking try_read/try_write on the lock wrapper under the new feature.
src/sync.rs Introduces ContendedResult and new Cache::try_* non-blocking operations gated by the feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +119 to +122
#[cfg(not(feature = "parking_lot"))]
{
self.0.try_read().ok().map(RwLockReadGuard)
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try_read() maps any Err from the non-parking_lot backends to None via .ok(), which silently treats a poisoned lock the same as contention. This changes behavior vs read() (which panics on poisoning) and also makes the docs inaccurate (poisoning isn't “held by a writer”). Consider matching on TryLockError and only returning None for WouldBlock, while panicking (or otherwise explicitly handling) Poisoned to keep semantics consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +275 to +291
/// 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<Q>(&self, key: &Q) -> ContendedResult<bool>
where
Q: Hash + Equivalent<Key> + ?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)
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New non-blocking APIs (try_contains_key/try_get/try_peek/try_remove/try_insert*) add important behavior (returning Contended instead of blocking) but there are no feature-gated tests exercising the contended path. Add #[cfg(feature = "non-blocking")] unit tests that hold a shard read/write lock (tests can use the private shard_for helper) and assert these methods return Contended without blocking, plus a basic success-path assertion.

Copilot uses AI. Check for mistakes.
Comment on lines 15 to 20
[features]
default = ["ahash", "parking_lot"]
non-blocking = []
sharded-lock = ["dep:crossbeam-utils"]
shuttle = ["dep:shuttle"]
stats = []
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new non-blocking feature gates public API items, but docs.rs is configured to build docs with only features = ["stats"]. That means the new ContendedResult and Cache::try_* methods won’t appear in published docs by default. Consider adding non-blocking to package.metadata.docs.rs.features (or documenting that users must enable it locally) so the new API is discoverable.

Copilot uses AI. Check for mistakes.
@fsdvh
Copy link
Copy Markdown
Author

fsdvh commented Apr 12, 2026

Thanks for the PR. In general I like the additions 👍 Do you think there's a practical benefit putting them behind the feature flag?

Putting it under a feature flag for convenience, if you think it belongs in the default feature set, I have no objections. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants