pub struct PackageCache { /* private fields */ }Expand description
The versioned, immutable, checksum-verified runner package cache.
Implementations§
Source§impl PackageCache
impl PackageCache
Sourcepub fn new(paths: &AppPaths, os: Os, arch: Arch, ports: CachePorts) -> Self
pub fn new(paths: &AppPaths, os: Os, arch: Arch, ports: CachePorts) -> Self
Build a cache rooted under paths, for a host running os/arch.
os and arch are parameters rather than probed here, following
d1’s pattern: runner_manager_platform::os::detect_host is called once
at the composition edge, and everything below takes the pair as a value.
That is the whole injection seam — there is no host-probe trait — and it
is what lets a Linux CI leg exercise the Windows selection path.
pub fn with_pins(self, pins: PinnedDigests) -> Self
pub const fn with_freshness(self, freshness: Freshness) -> Self
Sourcepub const fn with_retry_budget(self, budget: u32) -> Self
pub const fn with_retry_budget(self, budget: u32) -> Self
Override the retry budget. Zero is rejected in favour of one attempt, because a cache that never attempts anything is not a cache.
Sourcepub fn tool_cache_dir(&self) -> &Path
pub fn tool_cache_dir(&self) -> &Path
Where approved tool caches are retained.
A sibling of the package entries, not a child: an entry is immutable and
a tool cache is by definition written to. Both are retained across
attempts, and both are outside AppPaths::runtime_dir, which is where
disposable job workspaces live — that is the separation
05-infrastructure.md asks for.
Sourcepub async fn ensure_installed(&self) -> Result<InstalledPackage, PackageError>
pub async fn ensure_installed(&self) -> Result<InstalledPackage, PackageError>
Make a verified runner package available, and answer which one.
This is the cold-start path. It decides three things in order:
- Is this host one the product documents? An undocumented pair is refused here, before the catalog is consulted and long before anything is downloaded.
- Is a check due? The published version is re-checked on
Freshness::check_interval, and always when the cache is empty. A cold start inside that interval reuses what a previous one resolved, which is what “a bounded interval” buys. - Is what we hold still good enough? See
Self::is_stale.
§Errors
Any PackageError. A terminal one is returned on the spot and spends
none of the retry budget; a retryable one is retried up to
Self::with_retry_budget times and then wrapped in
PackageError::Exhausted.
Sourcepub fn is_stale(&self, package: &InstalledPackage, now: Timestamp) -> bool
pub fn is_stale(&self, package: &InstalledPackage, now: Timestamp) -> bool
Whether a cached entry has fallen outside the freshness window.
§Why install time, and why this is the fail-closed direction
GitHub’s rule is about the release: a runner more than
FRESHNESS_WINDOW_DAYS days behind the latest one is refused. The
agent cannot see release dates — GitHub’s runner-downloads response
carries no version field, let alone a publication timestamp — so the
supersession moment has to be bounded rather than read.
It can be bounded exactly. This cache only ever installs the version
GitHub was publishing at the time, so an entry installed at T was
the latest release at T. Whatever superseded it did so at some
R >= T, hence now - R <= now - T. So:
now - T <= windowprovesnow - R <= window: the entry cannot be past the deadline, and reusing it is safe.now - T > windowproves nothing either way, so the entry is treated as stale and refreshed.
The error is therefore always toward refreshing early, never toward running a package GitHub will refuse. Refreshing early costs a download; refreshing late costs every job on the host, for a reason the operator has no way to guess.
Sourcepub fn entry(
&self,
version: &RunnerVersion,
) -> Result<Option<InstalledPackage>, PackageError>
pub fn entry( &self, version: &RunnerVersion, ) -> Result<Option<InstalledPackage>, PackageError>
One entry, if it is installed and complete.
A directory with no readable manifest is not an entry: the manifest lands with the directory in a single rename, so its absence means the directory was not produced by this module and nothing may be assumed about its contents.
§Errors
PackageError::Io when the cache cannot be read.
Sourcepub fn installed(&self) -> Result<Vec<InstalledPackage>, PackageError>
pub fn installed(&self) -> Result<Vec<InstalledPackage>, PackageError>
Every complete entry, oldest version first.
§Errors
PackageError::Io when the cache directory cannot be read.
Sourcepub fn lease(
&self,
attempt: &RunnerAttempt,
version: &RunnerVersion,
) -> Result<(), PackageError>
pub fn lease( &self, attempt: &RunnerAttempt, version: &RunnerVersion, ) -> Result<(), PackageError>
Record that attempt holds version, so a prune cannot remove it.
Two refusals, both structural:
- A version that is not installed cannot be held.
- An attempt whose runtime directory is inside the cache root is refused outright. Job workspaces are disposable and per-attempt; a package entry is shared and immutable. A workspace nested in one would be destroyed the moment that version was pruned, and would mutate an entry that every other runtime is copied from. This is checked rather than documented because a documented invariant with no enforcement is how the property silently stops holding.
The lease survives a restart — it is a file, not a field — which is what makes the guard meaningful across the agent’s own lifetime.
§Errors
PackageError::NotInstalled, PackageError::WorkspaceInsideCache,
or PackageError::Io.
Sourcepub fn release(&self, attempt: AttemptId) -> Result<(), PackageError>
pub fn release(&self, attempt: AttemptId) -> Result<(), PackageError>
Drop attempt’s hold, whatever it was holding.
Idempotent: releasing a lease that was never taken is not an error, so a cleanup path may call it unconditionally.
§Errors
PackageError::Io when the lease file cannot be removed.
Sourcepub fn holders(
&self,
version: &RunnerVersion,
) -> Result<Vec<AttemptId>, PackageError>
pub fn holders( &self, version: &RunnerVersion, ) -> Result<Vec<AttemptId>, PackageError>
Every attempt currently holding version.
§Errors
PackageError::Io when the lease directory cannot be read.
Sourcepub fn sweep_staging(&self) -> Result<usize, PackageError>
pub fn sweep_staging(&self) -> Result<usize, PackageError>
Remove staging litter left by an interrupted install.
Nothing under .staging/ is ever part of an entry, so this is always
safe to call. It is separate from installing because a sweep that ran
automatically would race a concurrent install’s staging directory.
§Errors
PackageError::Io when the staging root cannot be read.