Skip to content

feat: sharded in-memory stores + #[concurrent_cached] default#264

Open
jaemk wants to merge 1 commit into
masterfrom
20250525.sharded-cache
Open

feat: sharded in-memory stores + #[concurrent_cached] default#264
jaemk wants to merge 1 commit into
masterfrom
20250525.sharded-cache

Conversation

@jaemk
Copy link
Copy Markdown
Owner

@jaemk jaemk commented May 25, 2026

Summary

  • Add six fully-concurrent sharded in-memory cache stores: `ShardedCache`, `ShardedLruCache`, `ShardedTtlCache`, `ShardedLruTtlCache`, `ShardedExpiringCache`, `ShardedExpiringLruCache` — each wraps an `Arc`, uses power-of-two `parking_lot::RwLock`-sharded buckets with cache-line padding, and supports builder APIs with `on_evict` callbacks, `copy_from` (live resharding), and `metrics()`/`shard_sizes()`
  • `#[concurrent_cached]` now defaults to an in-memory sharded store when `redis` and `disk` are both absent; `size`, `ttl`, `shards`, and `expires` attributes select the matching variant; `map_error` on the infallible in-memory path is a compile error (use it only with `redis`/`disk`/custom `ty`)
  • New macro attributes across `#[cached]`, `#[once]`, `#[concurrent_cached]`: `cache_err = true` (cache `Err` values), `cache_none = true` (cache `None` values), `result_fallback = true` on `#[concurrent_cached]` (return last-known-good `Ok` on `Err`; requires `ttl`; stale value held in primary cache slot via `ConcurrentCloneCached`, no separate store)
  • Remove `result = true` and `option = true` from `#[cached]`/`#[once]` (now inferred); add `cache_err`/`cache_none` opt-ins for the previous cache-everything behavior
  • Add `cache_remove_entry` / `cache_delete` to `Cached` and `ConcurrentCached`; `cache_clear_with_on_evict()` to all 13 stores; `StripedCounter` for reduced false-sharing on `UnboundCache`/`TtlSortedCache` metrics
  • Add `ConcurrentCloneCached<K, V>` trait — concurrent analogue of `CloneCached` — implemented by the four expiry-capable sharded stores; provides `cache_get_with_expiry_status` without evicting expired entries

Test plan

  • `make ci` passes (fmt + clippy + readme + full test suite including Redis)
  • Sharded store unit tests in `src/stores/sharded/`
  • `#[concurrent_cached]` integration tests in `tests/cached.rs` covering all six store variants, `cache_err = true`, `cache_none = true`, `result_fallback = true` (including `ShardedLruTtlCache` variant and prime_cache path), and `with_cached_flag` paths
  • Compile-fail tests in `tests/ui/` for invalid attribute combinations

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 25, 2026 20:04
@codspeed-hq

This comment has been minimized.

This comment was marked as resolved.

@jaemk jaemk force-pushed the 20250525.sharded-cache branch from 2f427df to fbc8261 Compare May 25, 2026 21:27
@jaemk jaemk requested a review from Copilot May 25, 2026 21:32

This comment was marked as resolved.

@jaemk jaemk force-pushed the 20250525.sharded-cache branch from fbc8261 to 3dc1867 Compare May 25, 2026 22:22
@jaemk jaemk requested a review from Copilot May 25, 2026 22:23

This comment was marked as resolved.

@jaemk jaemk force-pushed the 20250525.sharded-cache branch from 3dc1867 to f2bdc69 Compare May 26, 2026 00:17
@jaemk jaemk requested a review from Copilot May 26, 2026 00:17

This comment was marked as resolved.

@jaemk jaemk force-pushed the 20250525.sharded-cache branch from f2bdc69 to 2bace40 Compare May 26, 2026 01:03
@jaemk jaemk requested a review from Copilot May 26, 2026 01:04
@jaemk jaemk force-pushed the 20250525.sharded-cache branch from 2bace40 to aeddf09 Compare May 26, 2026 01:07

This comment was marked as resolved.

@jaemk jaemk force-pushed the 20250525.sharded-cache branch from aeddf09 to 52e6969 Compare May 26, 2026 01:16
Copilot AI review requested due to automatic review settings May 26, 2026 01:21
@jaemk jaemk force-pushed the 20250525.sharded-cache branch from 52e6969 to 1c7c966 Compare May 26, 2026 01:21

This comment was marked as resolved.

@jaemk jaemk force-pushed the 20250525.sharded-cache branch from 1c7c966 to 2473a37 Compare May 26, 2026 01:29
Copilot AI review requested due to automatic review settings May 26, 2026 02:21
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

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

Comment thread cached_proc_macro/src/lib.rs Outdated
Comment thread cached_proc_macro/src/lib.rs Outdated
Comment thread benches/cache_benches.rs Outdated
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

Copilot reviewed 125 out of 127 changed files in this pull request and generated 2 comments.

Comment thread docs/migrations/1.1-to-2.0-human.md Outdated
Comment on lines +147 to +151
**Impact on `cache_delete`:** `cache_delete` is a provided method that returns `true` when
`cache_remove` returns `Some`. Because expired entries return `None`, `cache_delete` returns
`false` even though the entry was physically removed. If you call `cache_delete` to detect
whether a key existed, use `cache_remove` instead and check whether the returned option is
`Some` (live entry) or `None` (expired or absent).
Comment thread docs/migrations/1.1-to-2.0.md Outdated

**Detection:** search for `.cache_remove(` / `.remove(` / `.cache_delete(` / `.delete(` calls on an expiring store.

**Action:** if the code relied on `Some(value)` for expired-but-present entries, add an explicit `is_expired()` check before removal, or ignore the return value. Also note that `cache_delete` / `delete` return `false` for expired entries that are physically removed — if you use `cache_delete` to check whether a key existed, switch to `cache_remove` and check whether the result is `Some` (live) or `None` (expired or absent).
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

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

Comment thread .agents/skills/pr-cycle/pr.py Outdated
Comment on lines +8 to +14
Commands (executed in the order given):
comments Fetch and display all inline PR review comments (new vs pre-existing)
threads List all review threads with resolution status
resolve Resolve all open review threads
rerequest Re-request Copilot review
minimize Minimize (hide) all Copilot review comments as "resolved"
codspeed Check CodSpeed benchmark results
Comment thread docs/migrations/1.1-to-2.0.md Outdated
Comment on lines +58 to +60
**Detection:** search for `.cache_remove(` / `.remove(` / `.cache_delete(` / `.delete(` calls on an expiring store.

**Action:** if the code relied on `Some(value)` for expired-but-present entries, add an explicit `is_expired()` check before removal, or ignore the return value. Also note that `cache_delete` / `delete` return `false` for expired entries that are physically removed — if you use `cache_delete` to check whether a key existed, switch to `cache_remove` and check whether the result is `Some` (live) or `None` (expired or absent).
Comment thread docs/migrations/1.1-to-2.0-human.md Outdated
Comment on lines +147 to +151
**Impact on `cache_delete`:** `cache_delete` is a provided method that returns `true` when
`cache_remove` returns `Some`. Because expired entries return `None`, `cache_delete` returns
`false` even though the entry was physically removed. If you call `cache_delete` to detect
whether a key existed, use `cache_remove` instead and check whether the returned option is
`Some` (live entry) or `None` (expired or absent).
Comment thread CHANGELOG.md Outdated
Comment on lines +3 to +12
## [Unreleased]

### Added
- `Cached::cache_remove_entry<Q>(&mut self, k: &Q) -> Option<(K, V)>`: new required method on the `Cached` trait that removes an entry and returns the stored key and value. Unlike `cache_remove`, this returns `Some` even when the deleted entry was already expired, making it possible to distinguish "key absent" from "key present but expired". Always fires the store's `on_evict` callback (if set).
- `ConcurrentCached::cache_remove_entry(&self, k: &K) -> Result<Option<(K, V)>, Self::Error>`: same semantics on the concurrent trait; implemented for all thirteen stores (seven non-sharded, six sharded).
- `Cached::cache_delete<Q>(&mut self, k: &Q) -> bool`: new default method on `Cached` that deletes an entry without returning it; returns `true` if an entry was physically removed (including expired entries), `false` if the key was absent. Implemented via `cache_remove_entry`.
- `DiskCache` and `RedisCache` / `AsyncRedisCache` now require `K: Clone` (in addition to existing bounds) for their `ConcurrentCached` / `ConcurrentCachedAsync` impls, which is needed to return the stored key from `cache_remove_entry`.

### Fixed
- `Cached::cache_delete` (now on `Cached` via `cache_remove_entry`) correctly returns `true` for entries that were present but already expired; previously `cache_delete` on `ConcurrentCached` returned `false` for expired entries.
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

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

Comment thread src/stores/sharded/mod.rs
Comment on lines +34 to +36
/// so spatial locality is a win. Counters use `Relaxed` atomics; on stores
/// that allow concurrent readers (read-lock paths), increments can race —
/// this is intentional, trading exactness for lower overhead.
Comment on lines +227 to +250
if args.cache_err && !is_result_return {
return syn::Error::new(
fn_ident.span(),
"`cache_err = true` requires the function to return `Result<T, E>`",
)
.to_compile_error()
.into();
}
if args.cache_none && !is_option_return {
return syn::Error::new(
fn_ident.span(),
"`cache_none = true` requires the function to return `Option<T>`",
)
.to_compile_error()
.into();
}
if args.cache_err && args.result_fallback {
return syn::Error::new(
fn_ident.span(),
"`cache_err` and `result_fallback` are mutually exclusive",
)
.to_compile_error()
.into();
}
Comment on lines +124 to +139
if args.cache_err && !is_result_return {
return syn::Error::new(
fn_ident.span(),
"`cache_err = true` requires the function to return `Result<T, E>`",
)
.to_compile_error()
.into();
}
if args.cache_none && !is_option_return {
return syn::Error::new(
fn_ident.span(),
"`cache_none = true` requires the function to return `Option<T>`",
)
.to_compile_error()
.into();
}
Comment thread .agents/skills/pr-cycle/pr.py Outdated
Comment on lines +302 to +308
print(f"Branch: {branch}")
print(f"\n--- CodSpeed workflow runs (last {limit}) ---")
r = gh("run", "list", "--workflow=codspeed.yml", f"--branch={branch}",
f"--limit={limit}", "--json", "databaseId,conclusion,createdAt,status", check=False)
if r.returncode != 0:
print("Could not fetch workflow runs.")
return
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

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

Comment thread .agents/skills/pr-cycle/pr.py Outdated
Comment on lines +303 to +308
print(f"\n--- CodSpeed workflow runs (last {limit}) ---")
r = gh("run", "list", "--workflow=codspeed.yml", f"--branch={branch}",
f"--limit={limit}", "--json", "databaseId,conclusion,createdAt,status", check=False)
if r.returncode != 0:
print("Could not fetch workflow runs.")
return
Comment thread src/stores/unbound.rs
Comment on lines +193 to +203
pub fn cache_clear_with_on_evict(&mut self) {
if self.on_evict.is_none() {
return self.cache_clear();
}
let entries: Vec<(K, V)> = self.store.drain().collect();
if let Some(on_evict) = &self.on_evict {
for (k, v) in &entries {
on_evict(k, v);
}
}
}
Comment thread src/stores/ttl.rs
Comment on lines +233 to +247
pub fn cache_clear_with_on_evict(&mut self) {
if self.on_evict.is_none() {
return self.cache_clear();
}
let entries: Vec<(K, TimedEntry<V>)> = self.store.drain().collect();
let count = entries.len() as u64;
if count > 0 {
self.evictions.fetch_add(count, Ordering::Relaxed);
}
if let Some(on_evict) = &self.on_evict {
for (k, entry) in &entries {
on_evict(k, &entry.value);
}
}
}
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

Copilot reviewed 125 out of 127 changed files in this pull request and generated 2 comments.

Comment thread benches/cache_benches.rs
Comment on lines +383 to +401
let mutex_map_w: Arc<Mutex<HashMap<usize, usize>>> = Arc::new(Mutex::new(HashMap::new()));
group.bench_function("Mutex<HashMap>", |b| {
b.iter_custom(|iters| {
let map = mutex_map_w.clone();
run_concurrent!(map, iters, t, i, {
map.lock().insert(write_key(i, t), i * 2);
})
})
});

let sharded_w = ShardedCache::<usize, usize>::new();
group.bench_function("ShardedCache", |b| {
b.iter_custom(|iters| {
let cache = sharded_w.clone();
run_concurrent!(cache, iters, t, i, {
cache.cache_set(write_key(i, t), i * 2).unwrap();
})
})
});
Comment thread .agents/skills/pr-cycle/pr.py Outdated
Comment on lines +302 to +308
print(f"Branch: {branch}")
print(f"\n--- CodSpeed workflow runs (last {limit}) ---")
r = gh("run", "list", "--workflow=codspeed.yml", f"--branch={branch}",
f"--limit={limit}", "--json", "databaseId,conclusion,createdAt,status", check=False)
if r.returncode != 0:
print("Could not fetch workflow runs.")
return
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

Copilot reviewed 125 out of 127 changed files in this pull request and generated 2 comments.

Comment thread docs/migrations/1.1-to-2.0.md Outdated
Comment on lines +125 to +126
ShardedExpiringCache<K, V> — unbounded, per-value TTL
ShardedExpiringLruCache<K, V> — LRU, per-value TTL
Comment thread .agents/skills/pr-cycle/pr.py Outdated
Comment on lines +291 to +306
def cmd_codspeed(pr, owner, repo, branch=None, limit=5):
section(f"PR #{pr} CodSpeed")

if branch is None:
r = gh("pr", "view", str(pr), "--json", "headRefName", "--jq", ".headRefName", check=False)
if r.returncode == 0 and r.stdout.strip():
branch = r.stdout.strip()
else:
print("Could not auto-detect branch. Pass --branch explicitly.")
return

print(f"Branch: {branch}")
print(f"\n--- CodSpeed workflow runs (last {limit}) ---")
r = gh("run", "list", "--workflow=codspeed.yml", f"--branch={branch}",
f"--limit={limit}", "--json", "databaseId,conclusion,createdAt,status", check=False)
if r.returncode != 0:
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

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

Comment thread src/stores/sharded/mod.rs
Comment on lines +31 to +34
/// (in practice always `CachePadded<Shard<S>>`). The lock word and the
/// hit/miss counters intentionally share a cache line: they are touched by
/// the same op (a `cache_get` acquires the lock and then bumps a counter),
/// so spatial locality is a win. Counters use `Relaxed` atomics; on stores
/// In the in-memory default path, `map_error` is optional and defaults to an `Infallible` shim.
/// Because the default sharded stores cannot fail, a `map_error` supplied on this path is accepted
/// but ignored (it is never invoked); reserve it for `redis`/`disk`/custom `ty`/`create` stores.
/// Functions may return a plain `T`, `Option<T>`, or `Result<T, E>`. Plain values are
Comment thread .agents/skills/pr-cycle/pr.py Outdated
Comment on lines +303 to +305
print(f"\n--- CodSpeed workflow runs (last {limit}) ---")
r = gh("run", "list", "--workflow=codspeed.yml", f"--branch={branch}",
f"--limit={limit}", "--json", "databaseId,conclusion,createdAt,status", check=False)
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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Breaking macro changes:
- `result = true` / `option = true` removed from `#[cached]` and `#[once]`; interceptors
  added with clear removal errors pointing to `cache_err`/`cache_none`
- `option = true` never valid on `#[concurrent_cached]`; error updated accordingly
- `result_fallback` no longer requires `result = true`; now requires `ttl` (compile guard)
- `map_error` on default sharded path is a compile error (was silently ignored)
- `cache_err + expires`, `cache_none + expires`, `result_fallback + expires`,
  `result_fallback + with_cached_flag`, `cache_none + with_cached_flag` all rejected
  at compile time with actionable diagnostics

New macro attributes:
- `cache_err = true` — opt-in to cache Err values (`#[cached]`, `#[once]`, `#[concurrent_cached]`)
- `cache_none = true` — opt-in to cache None values (same three macros)
- `result_fallback = true` on `#[concurrent_cached]` — returns last cached Ok on Err;
  requires `ttl`; stale TTL refreshed on every Err call; first-ever-failure returns raw Err
- `shards = N` on `#[concurrent_cached]` quick-reference table

New sharded concurrent stores:
- ShardedCache, ShardedLruCache, ShardedTtlCache, ShardedLruTtlCache,
  ShardedExpiringCache, ShardedExpiringLruCache — Arc-backed, per-shard parking_lot
  RwLock, builder APIs, on_evict callbacks, metrics, 128-byte cache-line padding
- `#[concurrent_cached]` defaults to in-memory sharded stores; `ty`/`create`/`map_error`
  optional on the in-memory path

New traits and methods:
- `ConcurrentCloneCached<K, V>` — returns stale cache entries with expiry status without
  evicting; implemented on four expiry-capable sharded stores
- `cache_remove_entry` added as required method to `Cached` and `ConcurrentCached`;
  returns Some for expired-but-present entries (unlike `cache_remove`)
- `cache_clear_with_on_evict()` on all 13 stores; fires on_evict per entry, increments
  eviction counter (plain `cache_clear()` remains side-effect-free)
- `cache_delete` added as default method on `Cached` (was `ConcurrentCached`-only)
- `cache_delete` on `ConcurrentCached` now returns true for expired entries (BREAKING)

Store behavior changes:
- `cache_remove` on expiring stores returns None for expired-but-present entries
- `is_result_return_type` / `is_option_return_type` use exact ident match; aliases
  like `MyResult<T>` are treated as plain values and Err is cached
- `LruCache::retain` fires on_evict and increments evictions counter
- All non-unbound stores fire on_evict on explicit `cache_remove`
- Rename `clear_with_on_evict` → `cache_clear_with_on_evict` (cache_ prefix convention)
- Rename `LruTtlInner::ttl_evictions` → `non_capacity_evictions`
- `DiskCache`/`RedisCache`/`AsyncRedisCache` `ConcurrentCached` impls now require `K: Clone`

Fixes and hardening:
- Fix use-after-move in `concurrent_cached` result_fallback for non-Copy key types
- Async result_fallback uses `ConcurrentCachedAsync` (.await) consistently
- Fix dead `is_smart_option` branch; Option<T> + non-default backend errors correctly
- result_fallback static visibility matches annotated function visibility
- Safe two-lookup pattern in TtlSortedCache (revert unsafe raw-pointer optimization)
- CACHE_LINE padding: `assert!()` replaces unused dead const
- `#[concurrent_cached]` store-conflict diagnostics (create/redis/disk) echo the
  user-written attribute name (`max_size` vs reconciled `size`)
- Document that explicit `shards = N` is rounded up to a power of two but not clamped
  to the 8–1024 default range (`size`/`max_size` attribute unaffected)
- Document that `CachedIter::iter()` (non-sharded `ExpiringCache`/`ExpiringLruCache` only)
  filters expired entries without removing them from the map
- Document that sharded reads clone values from under the shard lock, so `V: Clone`
- Remove stale 1.1-to-1.2 migration doc (version never released, content contradicted code)
- Stabilize flaky async CI test with timing-independent assertion

Tests:
- Integration tests for `#[concurrent_cached]` with `cache_err`, `cache_none`,
  `result_fallback` (sync + async, TTL expiry, first-ever-failure, non-Copy keys)
- Eviction counter tests for all 11 stores with eviction tracking
- cache_remove_entry coverage: counter increment, absent-key, stored-key identity
- Compile-fail tests for all new mutual-exclusion guards, including the `max_size`
  store-conflict diagnostic wording
- All sharded TTL tests moved into `time_store_tests` module (project convention)

Macro attribute deprecation:
- `size = N` on `#[cached]` / `#[concurrent_cached]` is now a deprecated alias for
  `max_size = N`; it still compiles but emits a deprecation warning (a hard error under
  `-D warnings` / `#![deny(deprecated)]`) steering users to `max_size`. Setting both on
  one annotation remains a compile error.
- Implemented via a `#[deprecated]` marker const (`__DEPRECATED_SIZE_ATTR`) referenced
  through `quote_spanned!` anchored at the user's `size` token, so the warning points at
  the attribute. All internal uses (tests, examples, doctests, README) migrated to
  `max_size`; dedicated alias-regression tests keep `size` covered under
  `#[allow(deprecated)]`, and trybuild UI tests pin the deprecation diagnostic.

Builder rename:
- Sharded builder `per_shard_size` -> `per_shard_max_size`, matching the `max_size`
  vocabulary used across the rest of the size-bound API.

Dev tooling (skills/agents, not shipped in the crate):
- New `pr-review` skill: standalone read-only review of a PR or checked-out branch
  (acquire diff, spawn code-review + consumer sub-agents in parallel, report findings
  with severity and a valid/already-fixed/invalid verdict). Review-agent model defaults
  to Sonnet, overridable per run (e.g. opus).
- `pr-cycle` refactored into the orchestrator: its review step delegates to `pr-review`
  instead of duplicating it; retains `full`/`local`/`remote` modes and the review-agent
  model override.
- `release` skill gains a pre-release `review` mode: runs a whole-crate consistency
  review plus an API-examination pass (via the `consumer-experience-review` skill) and
  reports lingering inconsistencies and a breaking/non-breaking improvement list;
  advisory only.
- Add `pr-code-reviewer`, `pr-consumer-reviewer`, `pr-fix-implementer` agent definitions
  the skills reference.

Version bump: 1.1 → 2.0.0
Migration guide: docs/migrations/1.1-to-2.0-human.md
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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

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