pub struct LocalDiskStore { /* private fields */ }Expand description
Local filesystem CAS: one file per content hash under root.
Implementations§
Source§impl LocalDiskStore
impl LocalDiskStore
pub fn new(root: impl Into<PathBuf>) -> LocalDiskStore
Trait Implementations§
Source§impl ContentStore for LocalDiskStore
impl ContentStore for LocalDiskStore
Source§fn put(&self, key: &InputHash, bytes: &[u8]) -> Result<(), Error>
fn put(&self, key: &InputHash, bytes: &[u8]) -> Result<(), Error>
Store bytes under key, atomically.
§#2 — Model R concurrent-writer safety
Before Model R, daemons were single-writer per process and
PID-scoped their CAS dir, so an in-place fs::write was adequate.
Model R’s whole point is a shared --cas-dir across a fleet of
daemons: N writers, multiple processes, one directory. An in-place
truncate-then-write is not safe there — a concurrent reader
doing contains()→get() can observe a half-written file.
Content-addressing guarantees the final bytes converge (same key
⇒ identical bytes); it does not make an interleaved read whole.
Fix: write to a unique sibling temp file, fsync it, then
rename it onto the final path. POSIX rename(2) within one
filesystem is atomic — a reader sees either the old state (absent)
or the complete new file, never a torn one. If another writer wins
the race the rename simply replaces an identical-content file
(content-addressed), so the result is still correct and the put
stays idempotent (the AC#5 cache-hit invariant). The exists()
fast-path is kept so a cache hit costs one stat, not a rewrite.
(Windows non-atomic-replace is out of scope — CLAUDE.md parks
Windows in v1; macOS + Linux are the supported fleet hosts.)