Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

#182

Bound and reclaim the runner's saved checkouts

merged · by keenan 5d ago · merged into main at 5d7417fe by keenan 5d ago

keenan opened this 5d ago

The runner already downloaded a repository once and cloned every job from that copy, but nothing bounded the copies and nothing reclaimed what they no longer needed. They grew with every tag, every branch, and every commit a job pinned by id, and a force-push left the old history reachable through the refs/remotes/origin/* reflogs for 90 days. A long-lived runner eventually filled its disk and then could not check anything out.

This is the second half of the same idea: the cache that saves a download now has a budget.

What changed

  • A cap, --git-cache-size (10G by default; 0 keeps every copy). Here a missing flag means the default, unlike --cache-size, because deleting a saved checkout only costs the next job a download.
  • Repack before delete. A copy holding more than its share of the cap, or any copy when the cache is over the cap, is repacked: leftover refs/vole/jobs/* from a killed runner are removed, the cache’s reflogs are made expendable, and git gc --prune=now drops everything no branch or tag can reach. Branch tips and tags stay, so a job for a cached revision is still a delta fetch.
  • Then delete the least recently used copy, whole. The repository this runner is about to build is never the one deleted, and a copy another process holds the lock on is passed over.
  • Bounded at startup and between jobs. Startup matches how the job cache is trimmed; the between-jobs pass skips --once, which is about to exit anyway. A repository under its share is repacked at most every six hours.
  • A per-repository lock (flock, under <dir>/.locks) around every read and write of a saved checkout: prepareWorktree (fetch, temporary ref, local clone), the repack, the deletion, and the site and release pushes that are written from the same cache. Without it, a --prune=now could delete objects another process was writing or cloning, and a deletion could land under a running job. The lock is per repository, so runners sharing a --dir still work in parallel on different repositories.
  • git clone --no-checkout for the saved copy. Nothing reads its working tree: records, sites and releases are written with plumbing, and each job gets a clone of its own, so the checkout only spent disk.

Two deliberate omissions

  • The branch fetch on a cache hit stays. Skipping it would make a cache hit fully offline, but a job’s checkout carries the cache’s tracking refs as origin/*, and a step comparing against origin/main would silently see an old branch. The fetch transfers no history; the download is what the cache already removes.
  • The repack does not run on a background goroutine. It is bounded, at most twice a day per repository once warm, and running it inline keeps the lock ordering honest: a background repack would just make the next job block inside prepareWorktree instead.

Testing

  • New tests in ci/agent cover the LRU deletion order, keeping the active repository’s copy, declining while another process holds the lock, and a repack dropping both a deleted branch’s commits and a leftover job ref while keeping the branch tip. The existing trusted-cache test now also asserts the saved copy has no working tree, must not be rebuilt when it is reused, and costs exactly one branch fetch on a hit.
  • vole workflow run verify passes (format, vet, build, licenses, govulncheck, packages, links, go test -race -count=1 ./...).

No issue is linked: this came from a direct request about runner bandwidth rather than from a tracked issue.

Discussion (0)

No discussion yet.