Caching docs: Cache is published once, after the pull, so a call makes at most two passes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-23 18:05:02 +02:00
commit 2db8d72e35
2 changed files with 12 additions and 8 deletions

View file

@ -144,11 +144,14 @@ which route wrote it.
that cache's gate as its own lock, so a store's load callback into the cache
cannot take the two out of order; a cacheless store takes its own gate. The
store reads which one applies, takes it, and checks the answer again under it:
`Cache` is written only by the attach, holding both gates, so under the cache's
gate `Cache` naming that cache cannot change, and under the store's gate it
cannot change at all. A call whose guess went stale (it queued on a cache that
then failed to attach, or on a store that then gained a cache) releases and
retries; a cacheless write that finds a cache is refused in that same hold. A
`Cache` is written only by the attach, holding both gates, once and after its
pull has succeeded (a failed attach never shows it, and nothing detaches), so
under the cache's gate `Cache` naming that cache cannot change, and under the
store's gate it cannot change at all. The pull reads the registry and the codec
the attach lent the store, not `Cache`. A call whose guess went stale (it
guessed no cache and the store then gained one) releases and retries once, so a
call makes at most two passes; a cacheless write that finds a cache is refused
in that same hold. A
store must not call `Loaded` while holding its own lock outside the cache's
hold.
- A journal runs under the cache's gate, so a slow journal slows every writer of

View file

@ -2261,9 +2261,10 @@ namespace GameCult.Caching
{
if (store == null) throw new ArgumentNullException(nameof(store));
homes ??= Array.Empty<Type>();
// The only writer of store.Cache, and it writes it only while holding the cache's gate and the store's, the failure path that
// resets Cache included: under the cache's gate Cache == this cache cannot change, and under the store's gate Cache cannot
// change at all. A store call that guessed wrong waits on whichever gate it took, finds the answer changed and retries.
// The only writer of store.Cache, and it writes it once, last, holding the cache's gate and the store's, after the pull has
// succeeded: a failed attach never publishes it, and there is no detach, so under the cache's gate Cache == this cache cannot
// change, and under the store's gate Cache cannot change at all. A store call that guessed null waits on the store's gate, finds
// the cache published and retries once.
Held(() => store.UnderStoreGate(() =>
{
if (store.Cache != null)