fix: serve fuse base blocks from the chunk cache and un-vacuum the epoch-lag test - #1300
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. WalkthroughThe PR adds genuine read-epoch rotation coverage for lagging folders and updates FUSE block caching to preserve hot blocks, avoid scan pollution, and apply per-handle truncation clamping. ChangesFUSE block cache read semantics
Read-epoch rotation coverage
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: ⚪ Minimal · up to The change routes base-block reads through the cache and makes the epoch-lag test exercise its intended rejection path; no actionable merge-blocking risk remains after normal checks and review. Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
675edf9 to
0951284
Compare
…och-lag test `OperationCore::version_block` read the base version straight off the engine and never touched `ChunkCache`, so every sub-block write, every `read_pending` window, and the whole commit walk re-fetched blocks the mount already held. It now reads through the cache. Blocks are cached whole and the per-handle truncate floor is clamped at use, because that floor belongs to the handle and not to the stream every handle on the file shares. A `Retain` argument says whether a path will come back to the bytes: the commit walk and the write-merge read through without retaining or promoting, so a linear pass over a file cannot spend the budget — or the recency order — on blocks it will never ask for again. `an_epoch_lagged_focus_folder_rejects_without_raising_abuse` asserted nothing. Raising the read-epoch floor directly revoked the cached scope read seed, so the focus-folder leg was skipped entirely and no rejection ever occurred. The harness now drives a real read-plane rotation: the root republishes at the next epoch under a fresh seed, which is what raises the floor and deposits the seed the focus leg needs, with an interior folder still publishing below it. A positive control in the same test re-seals those same children at the current epoch and watches them render, so the rejection cannot be a leg that never ran. Closes #1168 Closes #1218
0951284 to
94d9cc8
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
| Filename | Overview |
|---|---|
| crates/fuse/src/ops.rs | Adds cache-aware base-block reads with explicit hot/scan retention, whole-block caching, and per-use truncate clamping. |
| crates/fuse/src/cache.rs | Adds non-promoting cache lookup and centralizes empty-block admission rejection without changing the cache’s ownership model. |
| crates/fuse/tests/fuse_op_core.rs | Adds regression coverage for cached write merging, pending reads, truncate floors, and scan-induced eviction behavior. |
| crates/engine/tests/write_plane.rs | Reworks the epoch-lag test fixture to perform a real read-plane rotation and verifies recovery after sweeping the focused folder. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
V[version_block request] --> S{Spill contains block?}
S -->|Yes| P[Return spill block]
S -->|No| F{Inside base_len floor?}
F -->|No| Z[Return zero-filled block]
F -->|Yes| R{Retention mode}
R -->|Hot| G[Promoting cache get]
R -->|Scan| K[Non-promoting cache peek]
G --> H{Cache hit?}
K --> H
H -->|Yes| C[Clamp cached whole block to base_len]
H -->|No| E[Read whole block from pinned stream]
E --> C
C --> I{Hot miss?}
I -->|Yes| A[Insert whole block into cache]
I -->|No| O[Do not retain]
A --> Q[Return clamped block]
O --> Q
Reviews (1): Last reviewed commit: "fix: serve fuse base blocks from the chu..." | Re-trigger Greptile
What changed
version_blockbypasses the chunk cacheOperationCore::version_block(crates/fuse/src/ops.rs) read the base versionstraight off
engine.read_streamand never touchedChunkCache, so everysub-block write, every
read_pendingwindow, and the whole commit walkre-fetched base blocks the mount was already holding.
It now consults and fills the cache, with the two hazards the issue named
decided as it asked:
base_lenclamp is applied at use. A clamped, zero-padded block cached under(stream, index)would serve another handle on the same stream a hole its ownfloor never opened.
Retain { Hot, Scan }argument says whether a pathwill come back to these bytes.
push_version's linear commit walk and thewrite-merge (whose block is spilled on the next line) pass
Scan: they readthrough the cache and retain nothing, so neither spends a slot of the 64-block
budget on a block it will never ask for again.
Scanalso reads through a newChunkCache::peek, which serves without promoting — otherwise a whole-filewalk would re-rank a reader's hot blocks behind the blocks it touched once.
ChunkCache::insertnow refuses an empty block itself, so the admission policyhas one home instead of a guard duplicated at each call site.
An epoch-lagged focus-folder test that asserted nothing
an_epoch_lagged_focus_folder_rejects_without_raising_abuse(
crates/engine/tests/write_plane.rs) raised the read-epoch floor directly onthe device's floor store. That revoked the cached scope read seed, so
read_seedwasNoneand the focus-folder leg was skipped entirely — nofolder was ever resolved, no
EpochBelowFloorrejection ever occurred, and bothassertions held for free.
The harness now drives a real read-plane rotation:
rotate_read_epochrepublishes the vault root at the next read epoch under a freshly minted
override seed (via
reseal_scope_root, carrying the history link, write planestanding still). That is what raises the device's floor, and it deposits the new
seed in the same step, so
read_seedisSomeand the focus leg runs.photosis not swept, so its own writer keeps publishing at the old epoch andthe leg rejects it below the floor.
A positive control in the same test keeps it honest:
sweep_folderrepublishesthe same children re-sealed at the current epoch, and they render. The leg
was live for both passes, so the first outcome was a gate rejection and not a
leg that never ran.
Gate results
cargo fmt --all --check— clean.cargo clippy --workspace --all-targets -- -D warnings— clean.Both thread counts for
write_plane:No FUSE flakes observed; no test needed a re-run.
Mutation results
Every new assertion was verified against the un-fixed behaviour.
version_blockto the un-cached reada_sub_block_write_merges_over_the_block_the_read_path_cachedreading_through_pending_writes_fetches_each_base_block_oncea_cached_base_block_is_still_clamped_to_the_floor_a_shrink_leftpush_versionpassesRetain::Hotthe_commit_walk_leaves_a_readers_hot_blocks_in_the_cacheFor the vacuous-test fix, the negative control is the one the issue asked for —
invert the behaviour the test pins and it must fail:
rejection_verdict'sEpochBelowFloorarm (crates/engine/src/net/focus.rs)from
NonetoSome(RefreshVerdict::Rejected), making epoch lag attributable:the rewritten test fails on
an unswept folder is not an attacker. Thepre-existing test stays green under the identical mutation — the vacuity,
demonstrated rather than argued.
rotate_read_epochcall and keep everything else: the test failson
last-known-good stays pinned,left: ["2026", "2027"] right: ["2026"]—without the rotation the lagging record is simply adopted, so the rotation is
load-bearing.
The rejection is specifically the epoch stage, not a laundered unseal failure:
the lagging record is sealed under the retired seed, so an unseal-first gate
would classify it as a trust violation and raise abuse. No abuse event is
raised.
Review passes
/simplify— run ongit diff origin/main...HEAD(four parallel passes: reuse,simplification, efficiency, altitude). Applied:
ChunkCache::insert, removing the guard andits rationale from both call sites;
ChunkCache::peek, becauseRetain::Scanstill promoted throughget;HottoScan— the block it fetches is spilledon the next line, so retaining it spent a budget slot for no reuse;
clamp_intohelper, so the clamp is written once;rotate_read_epochnow reusesnet::author::{ENVELOPE_V, author_scope_root_with_section}and the file's ownpublish_next_recordinstead of hand-rolling seal → encode → publish. The fixture root now passes
the same release-active checks the production author path runs;
sweep_folderreads the folder's current children instead of taking ahand-built vec;
read_key_offactored ontoread_key_under(seed, node).Skipped, with reasons: folding
read()andversion_block()onto oneread-through helper — the two differ in plaintext ownership (
readhands thecache slice straight to
take_from;version_blockowns aZeroizingblock itclamps into), so a shared owned-return helper adds a full-block copy to
read'shot path and a borrowed return would drop the terminal-owner zeroize on the miss
path. Moving
rotate_read_epochintocrates/engine/src/testkit/owner_root.rsas a
resealed_owner_rootsibling is the right home but is outside this PR'sfile ownership — worth a follow-up.
/security-review— run on the same diff. No findings. What was traced:StreamHandles are monotonic and never recycled, so two handles on one file getdisjoint cache namespaces and a cross-handle serve is structurally impossible;
base_lenis monotonically non-increasing andclamp_intoruns on both the hitand the miss path;
read_pinned_rangehard-clamps the widened window to thepinned version, so the extra bytes can never come from another version, node, or
account; the retained block moves into
insert, which wraps inZeroizingbefore any early return, so even a refused block is wiped. The one design note it
raised — that the cache's own header stated its safety argument in terms of
version identity only — is folded in: the header now records that blocks are held
whole and clamped at use.
/crypto-privacy-review— not run.The diff touches no key or seal material:
crates/fuse/**movesalready-decrypted plaintext between the engine and the mount's existing
plaintext cache, and the
crates/engine/tests/write_plane.rschange istest-only, composing already-public engine and core primitives with the suite's
fixture constants.
Closes #1168
Closes #1218
Note
Fix fuse base block serving from chunk cache with hot/scan retention policy
Retainenum (Hot/Scan) inops.rsto distinguish cache-promoting reads (user reads) from non-promoting reads (write merges, commit walks).peekmethod toChunkCacheincache.rsthat returns a block without updating LRU order, used by scan paths.version_blocknow fetches full-sized blocks from the engine and clamps locally, fixing cases where base blocks were not served from cache correctly.readinops.rs.write_plane.rsto simulate a realistic read-epoch rotation with lazy sweep, replacing the previously vacuumed test.Macroscope summarized 94d9cc8.
Summary by CodeRabbit