Skip to content

Commit 3a08c52

Browse files
committed
Add entry/entry_async API for atomic cache access
- Add `entry()` / `entry_async()` with `FnOnce(&Key, &mut Val) -> EntryAction<T>` for atomic inspect-and-act under a write lock - `EntryAction<T>`: `Retain(T)`, `Remove`, `ReplaceWithGuard` - `EntryResult<T>`: `Retained(T)`, `Removed(K, V)`, `Replaced(Guard, V)`, `Vacant(Guard)`, `Timeout` - Placeholder deduplication: concurrent callers wait (blocking or async) and retry - Panic-safe weight accounting via `WeightGuard` drop guard - Shuttle concurrency tests and docs/README updates Closes #73, closes #77
1 parent 81c5a84 commit 3a08c52

9 files changed

Lines changed: 1422 additions & 175 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
command: clippy
107107
args: -- -D warnings
108108
- name: Install dependencies for tools
109-
run: sudo apt-get -y install libfontconfig1-dev jq
109+
run: sudo apt-get -y install libfontconfig1-dev jq --fix-missing
110110
- name: Check tools
111111
working-directory: tools
112112
run: cargo clippy -- -D warnings

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Lightweight and high performance concurrent cache optimized for low cache overhe
1212
* Scales well with the number of threads
1313
* Atomic operations with `get_or_insert` and `get_value_or_guard` functions
1414
* Atomic async operations with `get_or_insert_async` and `get_value_or_guard_async` functions
15+
* Closure-based `entry` API for atomic inspect-and-act patterns (keep, remove, replace)
1516
* Supports item pinning
1617
* Iteration and draining
1718
* Handles zero weight items efficiently
@@ -50,7 +51,7 @@ struct StringWeighter;
5051

5152
impl Weighter<u64, String> for StringWeighter {
5253
fn weight(&self, _key: &u64, val: &String) -> u64 {
53-
// Be cautions out about zero weights!
54+
// Be cautious about zero weights!
5455
val.len() as u64
5556
}
5657
}
@@ -64,6 +65,39 @@ fn main() {
6465
}
6566
```
6667

68+
Atomic inspect-and-act with the `entry` API
69+
70+
```rust
71+
use quick_cache::sync::{Cache, EntryAction, EntryResult};
72+
73+
fn main() {
74+
let cache: Cache<u64, u64> = Cache::new(100);
75+
76+
// Insert-or-get: if absent, compute and insert; if present, return cached
77+
let result = cache.entry(&0, None, |_key, val| EntryAction::Retain(*val));
78+
let value = match result {
79+
EntryResult::Retained(v) => v,
80+
EntryResult::Vacant(guard) => {
81+
let v = 42; // expensive computation
82+
guard.insert(v).unwrap();
83+
v
84+
}
85+
_ => unreachable!(),
86+
};
87+
assert_eq!(value, 42);
88+
89+
// Conditionally remove: evict entries below a threshold
90+
let result = cache.entry(&0, None, |_key, val| {
91+
if *val < 100 {
92+
EntryAction::<()>::Remove
93+
} else {
94+
EntryAction::Retain(())
95+
}
96+
});
97+
assert!(matches!(result, EntryResult::Removed(0, 42)));
98+
}
99+
```
100+
67101
Using the `Equivalent` trait for complex keys
68102

69103
```rust

src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
//! # Equivalent keys
1818
//!
1919
//! The cache uses the [`Equivalent`](https://docs.rs/equivalent/1.0.1/equivalent/trait.Equivalent.html) trait
20-
//! for gets/removals. It can helps work around the `Borrow` limitations.
21-
//! For example, if the cache key is a tuple `(K, Q)`, you wouldn't be access to access such keys without
20+
//! for gets/removals. It can help work around the `Borrow` limitations.
21+
//! For example, if the cache key is a tuple `(K, Q)`, you wouldn't be able to access such keys without
2222
//! building a `&(K, Q)` and thus potentially cloning `K` and/or `Q`.
2323
//!
2424
//! # User defined weight
@@ -31,6 +31,10 @@
3131
//! are available, they can be mix and matched) the user can coordinate the insertion of entries, so only
3232
//! one value is "computed" and inserted after a cache miss.
3333
//!
34+
//! The `entry` family of functions provide a closure-based API for atomically
35+
//! inspecting and acting on existing entries (keep, remove, or replace) while also coordinating
36+
//! insertion on cache misses.
37+
//!
3438
//! # Lifecycle hooks
3539
//!
3640
//! A user can optionally provide a custom [Lifecycle] implementation to hook into the lifecycle of cache entries.
@@ -114,7 +118,7 @@ pub type DefaultHashBuilder = std::collections::hash_map::RandomState;
114118
///
115119
/// impl Weighter<u64, String> for StringWeighter {
116120
/// fn weight(&self, _key: &u64, val: &String) -> u64 {
117-
/// // Be cautious out about zero weights!
121+
/// // Be cautious about zero weights!
118122
/// val.len() as u64
119123
/// }
120124
/// }

src/options.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl OptionsBuilder {
122122
self
123123
}
124124

125-
/// Builds an `Option` struct which can be used in the `Cache::with_options` constructor.
125+
/// Builds an `Options` struct which can be used in the `Cache::with_options` constructor.
126126
#[inline]
127127
pub fn build(&self) -> Result<Options, Error> {
128128
let shards = self.shards.unwrap_or_else(|| available_parallelism() * 4);

0 commit comments

Comments
 (0)