Skip to main content

Module cache

Module cache 

Source
Expand description

Durable ownership of the shared Cargo build cache (CARGO_TARGET_DIR).

One cache, many borrowers: an implement wave’s candidates, a review round’s E2E, the final gate, a human’s magi review invoked by hand — all of them may point CARGO_TARGET_DIR at the same directory, sometimes from different worktrees, sometimes from a different magi run entirely. Two failures came out of sharing it with no ownership record at all:

  • A read-only reviewer seat that inherited CARGO_TARGET_DIR tried to write into it, was refused by its own sandbox, and reported the refusal as a defect in the code under review rather than a property of its own seat. See graph::wave, which now only hands the variable to a seat that crate::agent::Invocation::allow_write actually permits to use.
  • Two source trees building the same package name/version into one CARGO_TARGET_DIR in sequence can leave a stale artifact from the older worktree looking fresh to Cargo, and a later cargo test runs binaries compiled from source nobody is looking at. ensure_fresh selectively cargo clean -ps the workspace’s own packages — never the downloaded dependency graph — the moment the recorded source identity for a cache directory changes.

Both are consequences of one missing fact: who is using this cache right now, and against which source. This module is that fact, made durable (a JSON file per cache directory, surviving a process restart) and cheap to ask (classify, in_use, inventory).

§Reclaiming a dead owner

Liveness is crate::proc::pid_alive — the same conservative check crate::daemon::sweep_stale_claims uses for the queue’s own claim locks: every uncertain outcome reads as alive, and a lease whose file cannot even be parsed is never reclaimed automatically (Status::Unknown). This is deliberately the same policy as the queue’s .lock files, not a new one — a bare lock file was never trusted alone there either; it is always paired with a liveness check.

§What this module does not do

It does not reap a build’s orphaned grandchildren once magi kills the seat that started them at a timeout — that is process-tree ownership, a different problem with its own owner elsewhere. A lease held by a process whose pid has exited is Stale and reclaimable as a lease, whether or not a grandchild is still writing files under the cache; this module only ever answers “who owns the cache directory”, not “is every process that might still be touching it definitely gone”.

Structs§

Entry
One cache directory’s state, for a0fc’s capacity/cleanup inventory (and magi cache show, eventually). Every lease file on disk is reported — including ones this process itself does not want to touch — because the question this answers is “what is registered right now”, not “what can I safely act on”.
Guard
A held lease. Releases on drop, so a panicking or early-returning caller never leaves the cache permanently marked busy — the same guarantee crate::queue::Claim gives the task queue’s own lock file.
Identity
The source a build against a cache directory was last known to come from. Compared by needs_refresh on every acquire, so a cache directory that only ever sees one worktree/head pair never pays a clean it does not need.
Owner
Who is holding, or wants, a cache directory.

Enums§

AcquireOutcome
What try_acquire returned.
Busy
Why try_acquire did not hand back a Guard.
EntryStatus
Entry::status’s possible values.
Status
Where a cache directory’s classification lands.

Functions§

ensure_fresh
Guarantee that a build against cache_dir from (worktree, head) never silently reuses another source’s compiled output: clean the workspace’s own packages out of the cache when the recorded identity disagrees, then record the new one. A no-op — no subprocess spawned — when the identity already matches, which is the common case once a cache directory settles on one worktree for a while.
in_use
Is cache_dir in use right now — held by a live owner, or in a state nobody can vouch for? The conservative half of the janitor’s prune guard: Status::Unknown refuses exactly like Status::Active, because a lease this process cannot read is not evidence the directory is free.
invalidate_identity
Forget the recorded source identity for cache_dir, so the next ensure_fresh call cannot skip its clean on the strength of a stale match.
inventory
Every cache directory magi knows about under home, whether or not anything holds it right now. A lease file reports the current borrower; once released it is gone (by design — see Guard::release), so a directory nobody is borrowing is reported from the catalog instead, carrying its last known owner rather than nothing at all. There is nothing here for a directory this process has never leased: inventory answers “what has magi registered”, not “what looks like a build cache”.
maintenance_prune
Prune cache_dir to limit, but only while holding the lease — a prune that raced a live build would delete files a compile in flight still needs, on top of confusing whatever built them about why its own output vanished. Ok(None) when the cache is in use right now; that is not an error, it is the janitor’s next pass catching it once the borrower is done. Never touches a lease file itself — they live outside cache_dir (see [leases_dir]), so crate::disk::prune_dir’s own sweep can never reach one.
needs_refresh
Has the source building against cache_dir changed since the last record? True (needs a refresh) whenever nothing was ever recorded — the conservative default for a cache directory this process has not tracked before.
parse_workspace_package_names
Parse cargo metadata --no-deps’s JSON for the names of packages defined in the workspace itself — never a dependency, which is exactly the distinction that keeps a freshness fix from also discarding a downloaded crate’s compiled artifacts on every worktree switch. Pure, so the parse is tested against fixture text without a cargo on the test machine.
record_identity
Persist identity as the last known source for cache_dir.
try_acquire
Take the lease for cache_dir if nobody live holds it, reclaiming a stale one first. One attempt — a caller that wants to wait uses wait_for, which is this in a loop bounded by a budget.
wait_for
Acquire the lease for cache_dir, waiting out contention rather than failing on the first busy owner — but never past budget, and the wait comes out of that same budget rather than a second, unbounded one. A caller already has a node timeout; this is that timeout, not a new clock next to it, which is what keeps a wait from becoming the “無期限待機” AGENTS.md’s own build-cache section warns is never acceptable. The error on timeout names who is still holding it, for the event this gets logged into.