Skip to content

Reshape lifecycle API: Default RequestState threaded by &mut (0.7.0)#124

Merged
arthurprs merged 4 commits into
masterfrom
evolve-lifecycle-with-state
Jun 25, 2026
Merged

Reshape lifecycle API: Default RequestState threaded by &mut (0.7.0)#124
arthurprs merged 4 commits into
masterfrom
evolve-lifecycle-with-state

Conversation

@arthurprs

Copy link
Copy Markdown
Owner

Summary

Reshapes the Lifecycle API so that operations needing lifecycle control have a single explicit form instead of a growing cross-product of per-operation variants (_with_lifecycle returning state, _with_state taking &mut, …).

This is a breaking change → 0.7.0. It supersedes the _with_state approach in #122.

What changed

  • Lifecycle::RequestState: Default — the request state is now constructed via Default and finalized by its own Drop. The begin_request and end_request trait methods are removed: every in-tree impl's begin_request was just Default::default(), and all lifecycle access already happens through &self in the eviction hooks, so neither method earned its place. Correctness (releasing evicted items outside the shard lock) now lives entirely in Drop for RequestState.

  • *_with_lifecycle methods take &mut L::RequestState (returning () / Result<(), _>) instead of returning the state. The &mut form is a strict superset of the old returning form — you own the state, so you can inspect evicted items and decide when to drop it — and it additionally lets you thread one state through several operations to batch the eviction work:

    let mut lcs = Default::default();
    cache.insert_with_lifecycle(k1, v1, &mut lcs);
    cache.insert_with_lifecycle(k2, v2, &mut lcs);
    // inspect `lcs` if desired; evicted items are released when it drops

    Applied uniformly across insert / try_insert / replace and the placeholder-guard inserts, on both sync and unsync. The ergonomic one-shots (insert, try_insert, replace) are unchanged.

  • Cleanup: removed the now-vestigial &L parameter threaded through the placeholder machinery (it existed only to feed end_request) and the unused Cache::lifecycle field — shards keep their own clones for the hooks.

Migration

// before
let lcs = cache.insert_with_lifecycle(k, v);
cache.lifecycle.end_request(lcs);

// after
let mut lcs = Default::default();
cache.insert_with_lifecycle(k, v, &mut lcs);
// `lcs` drops here, releasing evicted items

Custom Lifecycle impls: drop your begin_request/end_request (move any end_request work into Drop for RequestState) and ensure RequestState: Default.

Note / footgun

The default RequestState is [Option<_>; 2] — best-effort space for ~2 evictions. Threading one state through a large batch of inserts means evictions beyond the first two are dropped inside the shard lock. Callers who batch heavily should use a Vec-backed lifecycle. (Pre-existing behavior, but batching is now a blessed pattern, so worth calling out.)

Verification

  • cargo build --all-targets, cargo test (57 passed), cargo doc, cargo clippy — clean (one unrelated pre-existing bench warning).
  • RUSTFLAGS="--cfg fuzzing" cargo check in fuzz/ — clean; the fuzz targets exercise the new &mut inspect-evicted path.

Comment thread src/shard.rs Outdated
Comment thread Cargo.toml Outdated

@fsdvh fsdvh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks great!

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 #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.
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().
@arthurprs arthurprs force-pushed the evolve-lifecycle-with-state branch from 2c74856 to dbfcea7 Compare June 24, 2026 23:02
@arthurprs arthurprs marked this pull request as ready for review June 25, 2026 17:26
@arthurprs arthurprs merged commit 5ca720a into master Jun 25, 2026
12 checks passed
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.

2 participants