Files
bytecii 808b792a02 fix(cache): a recursive delete or directory rename evicts its subtree
`invalidate_subtree` had exactly one caller, the watcher. No `rm -r`,
recursive prefix delete or `rename` reached it in either language, so
every listing and body cached *below* a deleted or moved directory kept
being served: `ls` printed a deleted directory's contents and `cat`
returned a deleted file's bytes, both exit 0, until the index TTL
expired. Reproduced against live minio with the bucket verified empty,
so the reads were provably cache rather than survival.

Each mutation site reported `invalidate_after_unlink`, which evicts the
path's own listing and its parent's -- complete for a file, blind for a
subtree, because everything beneath was cached under its own key and
nothing above it evicts one.

`cache/context` gains an `invalidate_subtree` beside the two it already
had, and `CacheInvalidator` gains the method. It delegates to the
manager rather than walking: ancestors can be assembled from
`invalidate_after_write` calls because the caller knows the chain, but
only the caches know which keys lie beneath a path. Then one rule in
both languages -- an op that destroys or moves a whole subtree evicts
that subtree -- so every `rm_r`/`remove_prefix` and both endpoints of
every `rename` swap over. It is a strict superset of
`invalidate_after_unlink`, so a file path behaves exactly as before.

`unlink`, non-recursive `rmdir` and `copy` are deliberately untouched.

This came out of the kit plan's "delete-side `invalidate_ancestors`
asymmetry sweep", whose premise was wrong: `invalidate_ancestors` exists
because one op can materialize several levels at once, which no delete
does on a real-directory backend, and the keyed-store delete that does
need it already had it. box and gdrive only looked like they were
missing it -- their `mkdir -p` invalidates per created level in a
bespoke loop.

Also corrects two `emulate_truncate` docstrings that still named
s3/ssh/ram/redis as the emulating backends; #858 left dropbox as the
only one.

Pinned by `integ/resources/cache/subtree_evict.json`, 16 cases across
s3/s3-prefix/gridfs/gridfs-prefix, captured empirically on both hosts
and checked falsifiable by reverting the object_store half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:52:45 -07:00
..