Skip to main content

ContentAddressedMount

Struct ContentAddressedMount 

Source
pub struct ContentAddressedMount<S: ObjectStore + 'static = AnyStore> { /* private fields */ }
Expand description

In-mount overlay: a snapshot-time view of the parent state plus pending writes the agent has issued since.

Writes never modify the immutable state; they accumulate in [Pending] until ContentAddressedMount::capture folds them into a fresh state.

Implementations§

Source§

impl<S: ObjectStore + 'static> ContentAddressedMount<S>

Source

pub fn new( repo: Repository<RefManager, OpLog, S>, thread: impl Into<String>, ) -> Result<Self>

Open a writable mount of thread against repo.

Resolves the thread once, up front, so every subsequent lookup/read walks from a fixed snapshot. Writes accumulate in the pending tier until Self::capture folds them into a new state. To advance to a newer state, call Self::refresh.

Equivalent to Self::with_options with default options: a fresh per-mount blob cache. Daemon callers that want cross-mount cache reuse should construct an Arc<BlobCachePool> once and use with_options instead.

Source

pub fn with_options( repo: Repository<RefManager, OpLog, S>, thread: impl Into<String>, options: MountOptions, ) -> Result<Self>

Construct a mount with explicit options. Lets the caller share a blob cache across mounts in the same process — see MountOptions::blob_cache.

Source

pub fn blob_cache_pool(&self) -> &Arc<BlobCachePool>

Borrow the shared blob cache pool. Useful when the caller wants to spawn a BlobCachePool-aware pre-warmer or inspect cache stats.

Source

pub fn with_promotion_policy(self, policy: PromotionPolicy) -> Self

Override the promotion policy. Re-spawns (or terminates) the safety-sweep worker to honour the new sweep_interval. Mostly useful for tests that want a tight idle window or to disable idle-promotion entirely.

Source

pub fn refresh(&self) -> Result<()>

Re-resolve the thread and adopt the new state. Existing inodes are not invalidated — callers who want a clean slate should drop the mount and recreate.

Source

pub fn thread(&self) -> &str

The thread name this mount serves.

Source

pub fn current_change_id(&self) -> ChangeId

The change id this mount currently points at.

Source

pub fn clear_blob_cache(&self)

Drop every cached blob — both the mount-side LRU and the underlying ObjectStore’s recent_blobs/recent_trees caches. The next read on each blob pays full I/O + decompression cost. Exposed for benchmarks that want to measure the true cold-cache path without rebuilding the whole mount.

Source

pub fn prewarm(&self) -> PrewarmHandle

Spawn a background tree-walker that hydrates every file blob in the captured tree into the shared blob cache. The first kernel read after this finishes is served from memory at Arc::clone + memcpy cost — beats std::fs::read on every tier we benchmark.

The returned PrewarmHandle is the caller’s lever:

  • Drop it without calling anything → the prewarmer keeps running until natural completion or the mount drops (the workers hold Weak<MountInner> and self-terminate when the strong count hits zero).
  • .cancel() signals shutdown without joining.
  • .wait() joins all workers and returns the final stats.

Workers stop early when the cache is ≥ 90% full to avoid churn-evicting work they just did. Blobs already cached (from a sibling mount sharing the same pool) are skipped cheaply — this is the fork-thread fast path.

Source

pub fn lookup_path(&self, path: impl AsRef<Path>) -> Result<NodeId>

Resolve a mount-relative path to a NodeId. Used by tests that don’t go through lookup step-by-step.

Source

pub fn flush_node(&self, node: NodeId) -> Result<()>

Promote the hot buffer for node (if any) to a CAS blob and record it in the pending tree. Routed from the FUSE flush callback (per-descriptor-close). Orphaned nodes deliberately do nothing here — see [MountInner::flush_node] for the lifecycle rationale.

Source

pub fn release_node(&self, node: NodeId) -> Result<()>

Final close of node from a FUSE release callback. Decrements the open-handle refcount; on the last close, drops orphan state and (for non-orphans) promotes any surviving hot buffer.

Source

pub fn on_open(&self, node: NodeId) -> Result<()>

Notify the mount that a new open handle for node was minted (FUSE open / create callback). Used to time the orphan cleanup against the final close (see Self::release_node / [MountInner::release_node]).

Bumps the open count on the existing NodeState, minting a Live { open_count: 1 } entry if the node is untracked. An Orphan can also be opened (rare — only via an fh the kernel still holds across a re-lookup race); we bump its refcount so the final release fires correctly.

Mark path as deleted in the pending tier. Subsequent lookup/enumerate calls will skip the underlying captured entry, and capture() will fold the deletion into the new state’s tree (pruning empty parent dirs as needed).

Low-level (path-based) helper — unlike Self::unlink_entry it does not honour POSIX open-unlinked semantics. Used by tests that bypass the FUSE-callback lifecycle. The NodeId-keyed buffers for the path’s current owner are dropped (no orphan tracking).

Source

pub fn create_file( &self, parent: NodeId, name: &OsStr, mode: FileMode, exclusive: bool, ) -> Result<Entry>

Open-or-create a regular file under parent, mirroring open(O_CREAT[|O_EXCL]) from userspace.

When the named entry doesn’t exist, mints a fresh [NodeRecord::PendingFile] inode + an empty hot buffer so the new path is immediately visible to lookup / attrs and the first write drops cleanly into the existing two-tier model.

When the named entry already exists:

  • exclusive=trueMountError::AlreadyExists (errno EEXIST).
  • exclusive=false ⇒ returns the existing entry. The kernel follows up with setattr(size=0) for O_TRUNC callers, which we honour in set_attrs.
Source

pub fn make_dir(&self, parent: NodeId, name: &OsStr) -> Result<Entry>

Create an empty directory under parent. Recorded as an [Pending::explicit_dirs] entry so the new path is visible to lookup/enumerate even when no child has been written yet.

Source

pub fn unlink_entry(&self, parent: NodeId, name: &OsStr) -> Result<()>

Delete a regular file (or symlink) named name under parent.

POSIX open-unlinked semantics: the directory entry goes (path tombstoned, inodes.by_path[path] retired), but if any fd still references the inode, the bytes survive in hot[node] / warm[node] until the final release. Under the post-spike unified NodeId-keyed model (docs/design/mount-posix-semantics.md §1.2 T1/T2), this is a state transition only — no byte migration. Pre-spike code dropped pending.hot[node_id] here (Codex thread 3293307302 r9) and migrated warm[path] into orphan_warm[node] (r8); both steps go away.

Source

pub fn rmdir_entry(&self, parent: NodeId, name: &OsStr) -> Result<()>

Remove the empty directory name under parent. Fails with ENOTEMPTY if any child resolves through the mount.

Source

pub fn rename_entry( &self, old_parent: NodeId, old_name: &OsStr, new_parent: NodeId, new_name: &OsStr, ) -> Result<()>

Move (old_parent, old_name) to (new_parent, new_name). Handles file + symlink renames across any pair of overlay / captured paths, and overlay-only directory rename (a captured directory rename would require recursively rewriting the tombstone/warm map — out of scope for the cargo / git path).

Source

pub fn rename_entry_with_options( &self, old_parent: NodeId, old_name: &OsStr, new_parent: NodeId, new_name: &OsStr, options: RenameOptions, ) -> Result<()>

Same as Self::rename_entry but honours RenameOptions. no_replace (Linux RENAME_NOREPLACE) refuses the rename when the destination already resolves; the check is performed inside the same write-side critical section as the mutation, so a concurrent writer cannot install the destination between the check and the rename.

Source

pub fn set_attrs(&self, node: NodeId, update: AttrUpdate) -> Result<Attrs>

Apply attribute updates from a FUSE setattr / FSKit setattr / etc. Returns post-update Attrs for an inline reply.

Create a symbolic link under parent. Target bytes are kept in the pending tier verbatim; capture writes them as a CAS blob and emits a Symlink tree entry.

Read the target of a symlink node. Works for both overlay (PendingSymlink) and captured (Symlink) records.

Codex r12 thread 3293510316 (P1): the prior implementation used OsStr::from_encoded_bytes_unchecked on bytes loaded from the object store, which is unsound — that API’s safety contract requires bytes minted by OsStr::as_encoded_bytes in this process and Rust version, but captured-tree blobs can come from any process and version. The corrected path delegates to [symlink_target_from_bytes], which uses platform-safe APIs (OsStrExt::from_bytes on Unix, UTF-8 validation on Windows).

Source

pub fn flush_all(&self) -> Result<()>

Flush all hot buffers to CAS. Useful at the start of capture or when tests want a deterministic warm state.

Source§

impl ContentAddressedMount

Source

pub fn capture(&self, intent: impl Into<Option<String>>) -> Result<ChangeId>

Drain the pending tier into a fresh heddle state and update the thread to point at it.

This is the mount-side analogue of heddle capture/heddle snapshot: rather than walking a worktree to discover changed files, it folds the in-memory pending map into a real Tree object, records a State, and advances the thread’s HEAD ref.

intent is propagated to state.intent. Attribution is pulled from the repository’s default attribution path (Repository::get_attribution) — this honours the HEDDLE_AGENT_* env, the repo config, and the user’s principal. Richer attribution paths (CLI overrides, AgentRegistry, session segments) live in crates/cli/src/cli/commands/snapshot.rs::build_attribution; when the CLI wires this up it should call Self::capture_with_attribution instead and pass the result of that helper.

Source

pub fn capture_with_attribution( &self, intent: impl Into<Option<String>>, attribution: Attribution, ) -> Result<ChangeId>

Same as Self::capture but with caller-supplied attribution. The CLI uses this so it can mirror build_attribution from snapshot.rs (CLI overrides, agent registry lookup, etc.).

Trait Implementations§

Source§

impl<S: ObjectStore + 'static> Drop for ContentAddressedMount<S>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl<S: ObjectStore + 'static> PlatformShell for ContentAddressedMount<S>

Source§

fn lookup(&self, parent: NodeId, name: &OsStr) -> Result<Option<Entry>>

Look up name inside parent. Returns None for ENOENT.
Source§

fn read(&self, node: NodeId, offset: u64, buf: &mut [u8]) -> Result<usize>

Read up to buf.len() bytes from node, starting at offset. Returns the number of bytes actually written into buf.
Source§

fn write(&self, node: NodeId, offset: u64, data: &[u8]) -> Result<usize>

Write data to node at offset. Returns bytes written.
Source§

fn enumerate(&self, dir: NodeId) -> Result<Vec<Entry>>

List the children of dir.
Source§

fn attrs(&self, node: NodeId) -> Result<Attrs>

Stat node.
Source§

fn invalidate(&self, node: NodeId) -> Result<()>

Drop any cached identity for node. The platform layer calls this when the underlying state moves and previously-handed-out inode numbers may now point at the wrong content.
Source§

fn flush(&self, node: NodeId) -> Result<()>

Promote any hot-tier buffer for node into a CAS blob. The FUSE flush callback dispatches here (fires on close(2) and explicit fsync). Default: no-op for read-only mounts. Read more
Source§

fn release(&self, node: NodeId) -> Result<()>

Final close of node. The FUSE release callback dispatches here; it fires once per open(2) after the last fd derived from that open is closed. This is the canonical “last close of the inode” signal — it is the right hook (NOT Self::flush) for retiring per-inode lifecycle state like orphan-tracking markers or open-handle refcounts. Default: identical to flush so shells that do not maintain per-inode lifecycle state inherit a uniform contract.
Source§

fn on_open(&self, node: NodeId) -> Result<()>

Notify the shell that a new open file handle for node has been minted. FUSE adapters call this on the open / create callbacks so the shell can maintain a per-inode open-handle refcount — used to time the Self::release cleanup against the final close instead of the first one. Default: no-op so shells without lifecycle state are unaffected.
Source§

fn create_file( &self, parent: NodeId, name: &OsStr, mode: FileMode, exclusive: bool, ) -> Result<Entry>

Create a fresh regular file under parent. Mints a NodeId for the new file in the writable overlay and returns its Entry; subsequent write calls land in the per-thread hot tier. Read more
Source§

fn make_dir(&self, parent: NodeId, name: &OsStr) -> Result<Entry>

Create an empty directory under parent in the overlay. Returns the new directory’s Entry. Fails with MountError::AlreadyExists when name already resolves.
Source§

fn unlink_entry(&self, parent: NodeId, name: &OsStr) -> Result<()>

Delete the file named name under parent. The captured-tree entry (if any) is tombstoned so lookup / enumerate skip it; any pending-tier hot buffer or warm blob for the path is dropped. Read more
Source§

fn rmdir_entry(&self, parent: NodeId, name: &OsStr) -> Result<()>

Remove the empty directory named name under parent. Fails with MountError::NotADirectory for a file, with MountError::NotEmpty when the directory still has visible children (across captured tree + pending tier), or MountError::NotFound when nothing resolves.
Source§

fn rename_entry( &self, old_parent: NodeId, old_name: &OsStr, new_parent: NodeId, new_name: &OsStr, ) -> Result<()>

Atomically rename (old_parent, old_name) to (new_parent, new_name). Handles both same-directory and cross-directory cases. Replacing an existing entry of the same kind is allowed (POSIX semantics); replacing a directory with a file (or vice-versa) fails with MountError::IsADirectory / MountError::NotADirectory.
Source§

fn rename_entry_with_options( &self, old_parent: NodeId, old_name: &OsStr, new_parent: NodeId, new_name: &OsStr, options: RenameOptions, ) -> Result<()>

Same as Self::rename_entry but honours RenameOptions — in particular no_replace, which atomically refuses the rename when the destination already resolves. The check + the directory-entry mutation MUST happen under a single critical section to avoid a TOCTOU window between the existence check and the rename itself. Default: ignore options and dispatch to rename_entry (preserving the existing trait surface for shells that do not yet support flags).
Source§

fn set_attrs(&self, node: NodeId, update: AttrUpdate) -> Result<Attrs>

Apply attribute updates to node. Returns the post-update Attrs so callers can reply without a second getattr round trip. See AttrUpdate for which fields the overlay actually persists; unsupported fields are no-ops.
Create a symbolic link named name under parent whose target is the byte-equivalent of target. Returns the new link’s Entry.
Read the target of a symbolic link node. Returns the raw bytes of the link target (which may not be valid UTF-8 on some systems, hence OsString).

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more