mirror of
https://gitea.com/gitea/runner.git
synced 2026-08-29 15:27:45 +00:00
feat: add size-based cache eviction (#1170)
The cache server retired entries 30 days after creation regardless of use, so a job that ran often enough to keep its cache warm still lost it on a fixed schedule. Nothing bounded the disk either. Retention now counts from last access alone, and a repository over its limit sheds least recently accessed entries until it fits, enforced on commit as well as on the periodic sweep. ```yaml cache: retention: 168h # remove entries not accessed for seven days repo_size_limit: 10GB # cap each repository size_limit: 0 # cap the whole cache, off by default sweep_interval: 1h # minimum time between sweeps ``` Sizes accept `10GB`, `512mb`, `1TiB` or a plain byte count, binary either way. Leave a key out for its default; `0` turns a limit off, and `0s` does the same for `retention`. Whatever these allow, the cache also sheds entries to keep free space above `health_check.min_free_disk_space_mb` when health checks are enabled, so it cannot grow past the point where the runner stops accepting work. Supporting fixes: serving an entry stamps its access time, so a find cannot hand a job a download URL for an entry the next eviction is about to remove; an entry larger than the limit is dropped on its own account rather than emptying its repository to make room; and a blob that cannot be unlinked keeps its row, so the next sweep retries instead of orphaning bytes no limit can account for. Closes https://gitea.com/gitea/runner/issues/1168 --------- Co-authored-by: silverwind <me@silverwind.io> Reviewed-on: https://gitea.com/gitea/runner/pulls/1170 Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
This commit is contained in:
@@ -168,6 +168,37 @@ runner:
|
||||
assert.Equal(t, 5*time.Minute, cfg.Runner.PostTaskScriptTimeout)
|
||||
}
|
||||
|
||||
func TestLoadDefault_LoadsCacheEviction(t *testing.T) {
|
||||
write := func(t *testing.T, body string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||||
require.NoError(t, os.WriteFile(path, []byte(body), 0o600))
|
||||
return path
|
||||
}
|
||||
|
||||
t.Run("sizes accept any spelling of the unit", func(t *testing.T) {
|
||||
cfg, err := LoadDefault(write(t, "cache:\n retention: 336h\n repo_size_limit: 50gb\n size_limit: 1TiB\n sweep_interval: 15m\n"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 336*time.Hour, cfg.Cache.Retention)
|
||||
assert.Equal(t, Size(50*1024*1024*1024), cfg.Cache.RepoSizeLimit)
|
||||
assert.Equal(t, Size(1024*1024*1024*1024), cfg.Cache.SizeLimit)
|
||||
assert.Equal(t, 15*time.Minute, cfg.Cache.SweepInterval)
|
||||
})
|
||||
|
||||
t.Run("zero turns a limit off where an absent key keeps its default", func(t *testing.T) {
|
||||
cfg, err := LoadDefault(write(t, "cache:\n repo_size_limit: 0\n"))
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, cfg.Cache.RepoSizeLimit)
|
||||
assert.Equal(t, DefaultCache().Retention, cfg.Cache.Retention, "an absent key still defaults")
|
||||
})
|
||||
|
||||
t.Run("a bad size names the offending value", func(t *testing.T) {
|
||||
_, err := LoadDefault(write(t, "cache:\n repo_size_limit: banana\n"))
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "banana")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadDefault_LoadsJobHooks(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "config.yaml")
|
||||
|
||||
Reference in New Issue
Block a user