Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "quick_cache"
version = "0.6.24"
version = "0.7.0"
edition = "2021"
description = "Lightweight and high performance concurrent cache"
repository = "https://github.com/arthurprs/quick-cache"
Expand Down
2 changes: 0 additions & 2 deletions examples/eviction_listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ struct EvictionListener(mpsc::Sender<(u64, u64)>);
impl Lifecycle<u64, u64> for EvictionListener {
type RequestState = ();

fn begin_request(&self) -> Self::RequestState {}

fn on_evict(&self, _state: &mut Self::RequestState, key: u64, val: u64) {
let _ = self.0.send((key, val));
}
Expand Down
2 changes: 1 addition & 1 deletion fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 19 additions & 15 deletions fuzz/fuzz_targets/fuzz_sync_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ impl Weighter<u16, Value> for MyWeighter {
impl Lifecycle<u16, Value> for MyLifecycle {
type RequestState = Vec<(u16, Value)>;

fn begin_request(&self) -> Self::RequestState {
Default::default()
}

fn is_pinned(&self, _key: &u16, val: &Value) -> bool {
val.pinned
}
Expand Down Expand Up @@ -104,13 +100,15 @@ fn run(input: Input) {
match op {
Op::Insert(k, v, pinned) => {
// eprintln!("insert {k} {v}");
let evicted = cache.insert_with_lifecycle(
let mut evicted = Vec::new();
cache.insert_with_lifecycle(
k,
Value {
original: v,
current: v,
pinned,
},
&mut evicted,
);
placeholders.remove(&k);
// if k is present it must have value v
Expand All @@ -121,15 +119,20 @@ fn run(input: Input) {
Op::Replace(k, v, pinned) => {
// eprintln!("replace {k} {v}");
placeholders.remove(&k);
if let Ok(evicted) = cache.replace_with_lifecycle(
k,
Value {
original: v,
current: v,
pinned,
},
false,
) {
let mut evicted = Vec::new();
if cache
.replace_with_lifecycle(
k,
Value {
original: v,
current: v,
pinned,
},
false,
&mut evicted,
)
.is_ok()
{
// if k is present it must have value v
let peek = cache.peek(&k);
assert!(peek.is_none() || peek.unwrap().original == v);
Expand All @@ -155,7 +158,8 @@ fn run(input: Input) {
current: v,
pinned,
};
if let Ok(evicted) = p.insert_with_lifecycle(value) {
let mut evicted = Vec::new();
if p.insert_with_lifecycle(value, &mut evicted).is_ok() {
let peek = cache.peek(&k);
assert!(peek.is_none() || peek.unwrap().original == v);
check_evicted(k, peek, evicted);
Expand Down
45 changes: 26 additions & 19 deletions fuzz/fuzz_targets/fuzz_unsync_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ impl Weighter<u16, Value> for MyWeighter {
impl Lifecycle<u16, Value> for MyLifecycle {
type RequestState = Vec<(u16, Value)>;

fn begin_request(&self) -> Self::RequestState {
Default::default()
}

fn is_pinned(&self, _key: &u16, val: &Value) -> bool {
val.pinned
}
Expand Down Expand Up @@ -99,13 +95,15 @@ fn run(input: Input) {
match op {
Op::Insert(k, v, pinned) => {
// eprintln!("insert {k} {v}");
let evicted = cache.insert_with_lifecycle(
let mut evicted = Vec::new();
cache.insert_with_lifecycle(
k,
Value {
original: v,
current: v,
pinned,
},
&mut evicted,
);
// if k is present it must have value v
let peek = cache.peek(&k).copied();
Expand All @@ -125,15 +123,20 @@ fn run(input: Input) {
}
Op::Replace(k, v, pinned) => {
// eprintln!("replace {k} {v}");
if let Ok(evicted) = cache.replace_with_lifecycle(
k,
Value {
original: v,
current: v,
pinned,
},
false,
) {
let mut evicted = Vec::new();
if cache
.replace_with_lifecycle(
k,
Value {
original: v,
current: v,
pinned,
},
false,
&mut evicted,
)
.is_ok()
{
// if k is present it must have value v
let peek = cache.peek(&k).copied();
assert!(peek.is_none() || peek.unwrap().original == v);
Expand All @@ -146,11 +149,15 @@ fn run(input: Input) {
let (inserted, evicted) = match cache.get_ref_or_guard(&k) {
Ok(_) => (false, Vec::new()),
Err(g) => {
let evicted = g.insert_with_lifecycle(Value {
original: v,
current: v,
pinned,
});
let mut evicted = Vec::new();
g.insert_with_lifecycle(
Value {
original: v,
current: v,
pinned,
},
&mut evicted,
);
(true, evicted)
}
};
Expand Down
45 changes: 26 additions & 19 deletions fuzz/fuzz_targets/fuzz_unsync_cache_pinstate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ impl Weighter<u16, Value> for MyWeighter {
impl Lifecycle<u16, Value> for MyLifecycle {
type RequestState = Vec<(u16, Value)>;

fn begin_request(&self) -> Self::RequestState {
Default::default()
}

fn is_pinned(&self, _key: &u16, val: &Value) -> bool {
let mut pinned = val.pinned.get();
if pinned.remaining != 0 {
Expand Down Expand Up @@ -114,13 +110,15 @@ fn run(input: Input) {
match op {
Op::Insert(k, v, pinned) => {
// eprintln!("insert {k} {v}");
let evicted = cache.insert_with_lifecycle(
let mut evicted = Vec::new();
cache.insert_with_lifecycle(
k,
Value {
original: v,
current: v,
pinned: Cell::new(pinned),
},
&mut evicted,
);
// if k is present it must have value v
let peek = cache.peek(&k).cloned();
Expand All @@ -138,15 +136,20 @@ fn run(input: Input) {
}
Op::Replace(k, v, pinned) => {
// eprintln!("replace {k} {v}");
if let Ok(evicted) = cache.replace_with_lifecycle(
k,
Value {
original: v,
current: v,
pinned: Cell::new(pinned),
},
false,
) {
let mut evicted = Vec::new();
if cache
.replace_with_lifecycle(
k,
Value {
original: v,
current: v,
pinned: Cell::new(pinned),
},
false,
&mut evicted,
)
.is_ok()
{
// if k is present it must have value v
let peek = cache.peek(&k).cloned();
assert!(peek.is_none() || peek.as_ref().unwrap().original == v);
Expand All @@ -159,11 +162,15 @@ fn run(input: Input) {
let (inserted, evicted) = match cache.get_ref_or_guard(&k) {
Ok(_) => (false, Vec::new()),
Err(g) => {
let evicted = g.insert_with_lifecycle(Value {
original: v,
current: v,
pinned: Cell::new(pinned),
});
let mut evicted = Vec::new();
g.insert_with_lifecycle(
Value {
original: v,
current: v,
pinned: Cell::new(pinned),
},
&mut evicted,
);
(true, evicted)
}
};
Expand Down
38 changes: 24 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,31 @@ impl<Key, Val> Weighter<Key, Val> for UnitWeighter {
/// Hooks into the lifetime of the cache items.
///
/// The functions should be small and very fast, otherwise the cache performance might be negatively affected.
///
/// # Request state
///
/// Operations that may evict items thread a [`RequestState`](Lifecycle::RequestState)
/// through the eviction hooks. It is a per-request accumulator: the cache constructs a
/// fresh one via [`Default`], the `on_evict*`/`before_evict` hooks record into it, and it
/// is finalized by its own [`Drop`] (which, for example, releases evicted items _after_
/// the shard lock is dropped).
///
/// The `_with_lifecycle` cache methods take `&mut RequestState`, letting a caller drive
/// several operations against a single state — e.g. to batch the eviction work or inspect
/// evicted items before dropping it:
///
/// ```ignore
/// let mut lcs = Default::default();
/// cache.insert_with_lifecycle(k1, v1, &mut lcs);
/// cache.insert_with_lifecycle(k2, v2, &mut lcs);
/// // inspect `lcs` here if desired; evicted items are released when it drops
/// ```
pub trait Lifecycle<Key, Val> {
type RequestState;
/// Per-request accumulator threaded through the eviction hooks.
///
/// Constructed via [`Default`] at the start of each request and finalized by its
/// [`Drop`]. Keep it cheap to create and drop.
type RequestState: Default;

/// Returns whether the item is pinned. Items that are pinned can't be evicted.
/// Note that a pinned item can still be replaced with get_mut, insert, replace and similar APIs.
Expand All @@ -181,9 +204,6 @@ pub trait Lifecycle<Key, Val> {
false
}

/// Called before the insert request starts, e.g.: insert, replace.
fn begin_request(&self) -> Self::RequestState;

/// Called when a cache item is about to be evicted.
/// Note that value replacement (e.g. insertions for the same key) won't call this method.
///
Expand Down Expand Up @@ -233,16 +253,6 @@ pub trait Lifecycle<Key, Val> {
fn on_evict_hot(&self, state: &mut Self::RequestState, key: Key, val: Val) {
self.on_evict(state, key, val)
}

/// Called after a request finishes, e.g.: insert, replace.
///
/// Notes:
/// This will _not_ be called when using `_with_lifecycle` apis, which will return the RequestState instead.
/// This will _not_ be called if the request errored (e.g. a replace didn't find a value to replace).
/// If needed, Drop for RequestState can be used to detect these cases.
#[allow(unused_variables)]
#[inline]
fn end_request(&self, state: Self::RequestState) {}
}

/// The memory used by the cache
Expand Down
8 changes: 2 additions & 6 deletions src/shard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1325,7 +1325,7 @@ impl<
}
}

pub fn set_capacity(&mut self, new_weight_capacity: u64) {
pub fn set_capacity(&mut self, new_weight_capacity: u64, lcs: &mut L::RequestState) {
// Guard against division by zero when old capacity is 0 (produces inf/NaN ratios)
if self.weight_capacity == 0 {
self.weight_capacity = new_weight_capacity;
Expand All @@ -1344,11 +1344,7 @@ impl<
}

// Evict items if we're over the new capacity
let mut lcs = self.lifecycle.begin_request();
while self.weight_hot + self.weight_cold > self.weight_capacity
&& self.advance_cold(&mut lcs)
{}
self.lifecycle.end_request(lcs);
while self.weight_hot + self.weight_cold > self.weight_capacity && self.advance_cold(lcs) {}
// Trim ghost entries if needed
while self.num_non_resident > self.capacity_non_resident {
self.advance_ghost();
Expand Down
Loading
Loading