Skip to content

Add per item access count#6

Open
fsdvh wants to merge 20 commits into
masterfrom
expose-item-stats
Open

Add per item access count#6
fsdvh wants to merge 20 commits into
masterfrom
expose-item-stats

Conversation

@fsdvh

@fsdvh fsdvh commented Jun 24, 2026

Copy link
Copy Markdown
Owner

No description provided.

fsdvh and others added 20 commits May 12, 2026 16:11
### Summary

- Adds `try_read` / `try_write` to the internal `RwLock` wrapper, normalizing
  `WouldBlock` to `None` and panicking on poison (matching the blocking variants' behavior).
- Introduces a `LockContention` error type returned by all non-blocking operations
  when the target shard lock cannot be acquired immediately.
- Adds non-blocking counterparts for all major cache operations on `Cache`:
  - `try_contains_key` — returns `Result<bool, LockContention>`
  - `try_get` — returns `Result<Option<Val>, LockContention>`
  - `try_peek` — returns `Result<Option<Val>, LockContention>`
  - `try_remove` — returns `Result<Option<(Key, Val)>, LockContention>`
  - `try_insert` / `try_insert_with_lifecycle` — returns `Result<(), (Key, Val)>` /
    `Result<L::RequestState, (Key, Val)>` (key+value returned on contention so the
    caller can retry or discard)
- All read-path methods return `Err(LockContention)` on contention; write-path insert
  methods return `Err((key, val))` so the caller can retry or discard without losing data.
* extend lifecycle trait with on_evict_(cold|hot)

* default impl for on_evict

* Address Copilot review on PR arthurprs#117

- Fix hot/cold misclassification in handle_insert_overweight: capture the
  evicted entry's ResidentState before remove_internal so cold evictions
  aren't reported as hot.
- Reword on_evict deprecation note to accurately describe per-method
  fallback (each new method delegates to on_evict independently).
- Document rejection-style behavior on on_evict_hot: items that never
  enter the cache (oversized inserts and oversized placeholder values)
  are reported as hot.
- Migrate DefaultLifecycle (sync and unsync) and the eviction_listener
  example to implement on_evict_hot/on_evict_cold directly.

* Lifecycle: drop on_evict deprecation; route overweight rejections to cold

- on_evict stays as a provided method with an empty default body (no
  deprecation). on_evict_hot/on_evict_cold default to delegating to it,
  so existing impls keep working and new impls can override just the
  hot/cold methods when they want to distinguish.
- Route oversized inserts and oversized placeholder rejections through
  on_evict_cold (they never entered any queue).
- Revert DefaultLifecycle (sync/unsync) and the eviction_listener
  example to the simple on_evict form.

* Cross-reference rejection routing on on_evict and on_evict_hot docs

---------

Co-authored-by: Deniz <DenizUgur@users.noreply.github.com>
Co-authored-by: Arthur Silva <arthurprs@gmail.com>
* fix: memory leak in dropping uninserted guard

* fix

* Add regression tests for placeholder slot reuse on guard drop

Cover the two documented cases where a placeholder is removed/replaced
while a guard is still outstanding (overwrite insert, and remove + slot
reuse). Both panicked under the original unconditional entries.remove and
pass with the fix that only frees the slot when the placeholder is still
present.

---------

Co-authored-by: Arthur Silva <arthurprs@gmail.com>
`std::mem::size_of` was only added to the prelude in Rust 1.80, but the
declared MSRV is 1.71. It was used unqualified in linked_slab.rs, breaking
builds for downstream crates on Rust < 1.80.

Qualify the call as `std::mem::size_of` (matching shard.rs) and add an
`msrv` CI job that runs `cargo msrv verify` so this regression is caught
in the future.

Fixes arthurprs#118
reserve() only grew the hash table, not the LinkedSlab Vec that holds the
actual entries. Under a large insert burst, that Vec doubled and memcpy'd
hundreds of MB while the shard write lock was held, stalling all concurrent
readers on that shard (issue arthurprs#119).

Now reserve() also pre-allocates the slab, sized for the requested entries
plus the bounded ghost-key space (capacity_non_resident) instead of a fixed
50% guess.
The previous reserve() unconditionally added the full per-shard
capacity_non_resident as ghost headroom. On a cache with a large
estimated_items_capacity, a small reserve(n) call would then allocate
the entire ghost cap (potentially gigabytes), not space for n entries.

Cap the ghost term at additional: n insertions evict at most n residents
into ghosts, and ghosts are also bounded by capacity_non_resident, so
min(additional, capacity_non_resident) is the true upper bound. Full
pre-loads (additional >= cap) are unchanged; small reserves no longer
over-allocate.
…rthurprs#124)

* Reshape lifecycle API: Default-built RequestState threaded by &mut

BREAKING (0.7.0). Collapses the growing per-operation variant cross-product.

- `Lifecycle::RequestState` now requires `Default`; `begin_request` and
  `end_request` are removed. State is built via `Default` and finalized by
  its own `Drop` (which releases evicted items outside the shard lock).
- The `*_with_lifecycle` methods now take `&mut L::RequestState` (returning
  `()` / `Result<(), _>`) instead of returning the state, so one state can be
  threaded through several ops to batch eviction work or inspect evicted items.
  This supersedes the `_with_state` proposal in arthurprs#122.
- Drops the now-vestigial `&L` plumbing from the placeholder machinery and the
  unused `Cache::lifecycle` field; shards keep their own clones for the hooks.

* Drop RequestState outside the lock in set_capacity

set_capacity created and dropped its own RequestState inside the shard
method, so when called via Cache::set_capacity (shard.write().set_capacity)
the evicted items were released while the write lock was still held —
contradicting the documented Drop-after-lock contract. Take &mut lcs from
the caller instead, so it drops after the lock guard.

Also use Default::default() consistently instead of L::RequestState::default().

* Document lcs/lock drop ordering in set_capacity
…s, document stats overhead

- Fold record_item_hit{,_mut} into arity-overloaded record_hit{,_mut}
  arms so each resident hit-site records both counters in one call.
- Re-enable entry_overhead under the stats feature; assert the real
  per-cache sizes (sync unchanged, unsync +8 via discriminant niche).
- Document the stats feature's per-entry size and per-hit/miss cost.
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.

4 participants