feat(cache): purge stale cargo cache entries in retention run
TODO 2.9 recorded a retention policy that warned about low disk while freeing 0.0 GB twice in a row, because it governed artifacts, logs and IP leases -- 52 KB in total on this host -- while 195 G of backups sat outside its reach. Adding an unbounded directory of ~645 MB entries, one per Cargo.lock change, without extending retention in the same change would repeat that at a larger scale. The window is the tighter of the global retention and the cache's own, so the aggressive low-disk mode reaches the cache rather than stopping at the light directories. Sizes are read before deleting, not after. Summing st_size over files that have already been unlinked yields 0 -- which is exactly how a policy ends up reporting that it freed nothing while doing real work. A test asserts the reported figure.
This commit is contained in:
@@ -14,7 +14,8 @@ from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from ci_orchestrator.config import load_config
|
||||
from ci_orchestrator.cargo_cache import CargoCacheStore
|
||||
from ci_orchestrator.config import Config, load_config
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -93,6 +94,7 @@ def retention_run(
|
||||
|
||||
_purge_old_dirs(art_path, "artifact", cutoff, what_if=what_if)
|
||||
_purge_old_dirs(log_path, "log", cutoff, what_if=what_if)
|
||||
_purge_cargo_cache(config, effective_days, what_if=what_if)
|
||||
|
||||
usage_after = shutil.disk_usage(str(check_path))
|
||||
free_gb_after = usage_after.free / (1024**3)
|
||||
@@ -132,3 +134,44 @@ def _purge_old_dirs(base: Path, label: str, cutoff: datetime, *, what_if: bool)
|
||||
else:
|
||||
shutil.rmtree(d, ignore_errors=True)
|
||||
click.echo(f"[RetentionPolicy] Purged {label} ({age_days}d): {d.name}")
|
||||
|
||||
|
||||
def _purge_cargo_cache(config: Config, effective_days: int, *, what_if: bool) -> None:
|
||||
"""Purge stale cargo cache entries.
|
||||
|
||||
Entries are the heaviest thing this policy governs -- hundreds of megabytes
|
||||
each, one per Cargo.lock change -- so leaving them out would repeat TODO
|
||||
2.9, where retention warned about low disk while freeing 0.0 GB because
|
||||
everything that actually weighed was outside its reach.
|
||||
|
||||
The window is the tighter of the global retention and the cache's own, so
|
||||
the aggressive low-disk mode reaches the cache too.
|
||||
"""
|
||||
if not config.cargo_cache.enabled:
|
||||
click.echo("[RetentionPolicy] cargo cache: disabled — skipping.")
|
||||
return
|
||||
|
||||
days = min(effective_days, config.cargo_cache.retention_days)
|
||||
cutoff = datetime.now(tz=UTC) - timedelta(days=days)
|
||||
store = CargoCacheStore(config.cargo_cache.root)
|
||||
|
||||
# Size the victims first: after a real prune the files are gone and their
|
||||
# bytes are unreadable, which is how a policy ends up reporting 0.0 GB
|
||||
# freed while doing real work (TODO 2.9).
|
||||
doomed = store.prune_older_than(cutoff, what_if=True)
|
||||
if not doomed:
|
||||
click.echo(
|
||||
f"[RetentionPolicy] cargo cache: nothing to purge "
|
||||
f"(cutoff: {cutoff.strftime('%Y-%m-%d')})."
|
||||
)
|
||||
return
|
||||
freed_mb = sum(p.stat().st_size for p in doomed if p.exists()) / 1_048_576
|
||||
|
||||
if not what_if:
|
||||
store.prune_older_than(cutoff)
|
||||
|
||||
verb = "WhatIf: would purge" if what_if else "Purged"
|
||||
click.echo(
|
||||
f"[RetentionPolicy] {verb} {len(doomed)} cargo cache "
|
||||
f"{'entry' if len(doomed) == 1 else 'entries'} ({freed_mb:.0f} MB)"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user