@@ -23,48 +23,70 @@ and where the gaps are.
2323
2424## The dominant pattern: sequential core, concurrent wrapper
2525
26- Every concurrent type in cachekit follows the same shape:
26+ cachekit's concurrent types all keep the sequential core unaware of locking,
27+ but they do ** not** all have the same struct shape. There are three families.
28+
29+ ### Cloneable policy handles
30+
31+ Policy-level wrappers are shared handles around a locked policy core:
2732
2833``` text
29- ConcurrentX <K, V> { inner: Arc<RwLock<X <K, V>>> }
34+ ConcurrentPolicy <K, V> { inner: Arc<RwLock<Policy <K, V>>> }
3035```
3136
32- where ` X ` is the single-threaded core (` LruCore ` , ` FifoCache ` ,
33- ` S3FifoCache ` , ` SlotArena ` , ` IntrusiveList ` , ` ClockRing ` ,
34- ` HashMapStore ` , ` SlabStore ` , ` WeightStore ` , ` HandleStore ` ,
35- ` FrequencyBuckets ` ). The wrapper:
37+ This shape is used by:
3638
37- 1 . holds the core behind an ` Arc<RwLock<…>> ` ,
38- 2 . presents owned/` Arc<V> ` returns instead of borrowed ` &V ` ,
39- 3 . is ` Clone ` via ` Arc::clone ` so callers can hand copies to threads,
40- 4 . is ` Send + Sync ` because the inner core's ` Send + Sync ` impls
41- auto-derive through ` Arc<RwLock<…>> ` .
39+ - ` ConcurrentLruCache ` — [ ` src/policy/lru.rs ` ] ( ../../src/policy/lru.rs )
40+ - ` ConcurrentFifoCache ` — [ ` src/policy/fifo.rs ` ] ( ../../src/policy/fifo.rs )
41+ - ` ConcurrentS3FifoCache ` — [ ` src/policy/s3_fifo.rs ` ] ( ../../src/policy/s3_fifo.rs )
4242
43- The pattern is verbose but consistent and was chosen for three reasons:
43+ These types implement ` Clone ` via ` Arc::clone ` , so callers can hand cheap
44+ handles to threads. They expose owned / ` Arc<V> ` returns instead of borrowed
45+ ` &V ` because no reference can safely outlive the lock guard it came from.
4446
45- - ** No ` &mut self ` in the public API.** Sharing requires interior
46- mutability; an ` RwLock ` is the cheapest tool that exposes both shared
47- reads and exclusive writes through ` &self ` .
48- - ** The sequential core stays unaware of locking.** Policy code under
49- [ ` src/policy/ ` ] ( ../../src/policy ) is single-threaded and easier to
50- reason about. The locking discipline lives in one place per type.
51- - ** The lock is replaceable.** Swapping ` parking_lot::RwLock ` for a
52- different primitive (` std::sync::RwLock ` , a sharded lock, a seqlock)
53- is a local change because the inner core has no opinion on it.
47+ ### Owning store and data-structure wrappers
5448
55- The 11 concurrent wrappers shipped today live in:
49+ Store and data-structure wrappers usually own the lock directly:
50+
51+ ``` text
52+ ConcurrentX<K, V> { inner: RwLock<X<K, V>>, ... }
53+ ```
54+
55+ Examples:
5656
57- - ` ConcurrentLruCache ` — [ ` src/policy/lru.rs ` ] ( ../../src/policy/lru.rs )
58- - ` ConcurrentFifoCache ` — [ ` src/policy/fifo.rs ` ] ( ../../src/policy/fifo.rs )
59- - ` ConcurrentS3FifoCache ` — [ ` src/policy/s3_fifo.rs ` ] ( ../../src/policy/s3_fifo.rs )
6057- ` ConcurrentHashMapStore ` , ` ShardedHashMapStore ` — [ ` src/store/hashmap.rs ` ] ( ../../src/store/hashmap.rs )
6158- ` ConcurrentSlabStore ` — [ ` src/store/slab.rs ` ] ( ../../src/store/slab.rs )
6259- ` ConcurrentWeightStore ` — [ ` src/store/weight.rs ` ] ( ../../src/store/weight.rs )
6360- ` ConcurrentHandleStore ` — [ ` src/store/handle.rs ` ] ( ../../src/store/handle.rs )
64- - ` ConcurrentSlotArena ` , ` ShardedSlotArena ` — [ ` src/ds/slot_arena.rs ` ] ( ../../src/ds/slot_arena.rs )
61+ - ` ConcurrentSlotArena ` — [ ` src/ds/slot_arena.rs ` ] ( ../../src/ds/slot_arena.rs )
6562- ` ConcurrentIntrusiveList ` — [ ` src/ds/intrusive_list.rs ` ] ( ../../src/ds/intrusive_list.rs )
6663- ` ConcurrentClockRing ` — [ ` src/ds/clock_ring.rs ` ] ( ../../src/ds/clock_ring.rs )
64+
65+ These wrappers are not necessarily cloneable handles. If a caller wants shared
66+ ownership, they can wrap the whole type in ` Arc<_> ` . Keeping the ` Arc ` out of
67+ the struct avoids an unnecessary refcount on users who only need a single owner.
68+
69+ ### Sharded primitives
70+
71+ Sharded types own multiple independently locked shards:
72+
73+ ``` text
74+ ShardedX<K, V> {
75+ shards: Vec<RwLock<ShardState<K, V>>>,
76+ selector: ShardSelector,
77+ }
78+ ```
79+
80+ Examples:
81+
82+ - ` ShardedSlotArena ` — [ ` src/ds/slot_arena.rs ` ] ( ../../src/ds/slot_arena.rs )
6783- ` ShardedFrequencyBuckets ` — [ ` src/ds/frequency_buckets.rs ` ] ( ../../src/ds/frequency_buckets.rs )
84+ - ` ShardedHashMapStore ` — [ ` src/store/hashmap.rs ` ] ( ../../src/store/hashmap.rs )
85+
86+ The common design is not "` Arc<RwLock<_>> ` everywhere"; it is ** lock at the
87+ wrapper boundary and keep the sequential core lock-free** . The exact ownership
88+ shape depends on whether the type is intended to be a cloneable cache handle,
89+ an owning concurrent store, or a sharded primitive.
6890
6991## Why ` Concurrent* ` does not implement ` Cache<K, V> `
7092
@@ -271,8 +293,8 @@ When sharding is **not** what you want:
271293
272294## Concurrent policy coverage
273295
274- Of the 17 implemented policies, ** 3 ship with a ` Concurrent* ` wrapper
275- today** : LRU, FIFO, S3-FIFO. The remaining 14 require external locking
296+ Of the 18 implemented policies, ** 3 ship with a ` Concurrent* ` wrapper
297+ today** : LRU, FIFO, S3-FIFO. The remaining 15 require external locking
276298by the caller — typically ` Arc<parking_lot::RwLock<CacheCore>> ` . The
277299relevant rustdoc on those policies (e.g. ` LfuCache ` , ` HeapLfuCache ` ,
278300` MfuCache ` ) calls this out.
@@ -282,7 +304,7 @@ mechanical: wrap the sequential core in `Arc<RwLock<…>>`, expose the
282304` &self ` API with ` Arc<V> ` returns, decide read-lock vs. write-lock per
283305method, implement ` Clone ` via ` Arc::clone ` , and implement
284306` unsafe impl ConcurrentCache ` . The work is bounded; what's missing is
285- the discipline to do it consistently across all 17 policies.
307+ the discipline to do it consistently across all 18 policies.
286308
287309## Failure modes
288310
0 commit comments