vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Order cache eviction by written files, not directory mtimes
e6f0d8b947d985c8615e1172aef06cee8fd3729e · Keenan Nemetz · 20h ago · parent 973fb809
Order cache eviction by written files, not directory mtimes Trim sorted namespaces by the newest mtime in the tree, directories included. A directory's mtime moves when a tree is unpacked, so two namespaces created in the same breath compared equal and sort.Slice broke the tie arbitrarily. The runner's verify run on 01M6YSX585GD5BDT45H0FE65AE removed the newer namespace and left the older one, failing TestTrimDropsTheOldestNamespaceWhole on a machine where it had passed on ext4. measure now dates a namespace by its files, falling back to the directory's own mtime when there is no file, and Trim breaks an exact tie by path so the same namespace goes on every pass. TestTrimOrdersByContentNotDirectories fails on the previous implementation with the same message the runner printed.
ci/cache/cache.go +28−4
| @@ -145,6 +145,10 @@ func holdNamespace(root, ns string, exclusive, wait bool) (func(), bool, error) | |||
| 145 | 145 | // unknown state; a skipped namespace still counts against the cap, so the next pass | |
| 146 | 146 | // finds it again. | |
| 147 | 147 | // | |
| 148 | // "Written" is a file: see measure. Namespaces that were written at the same | ||
| 149 | // instant are ordered by path, so two caches of the same age always go in the same | ||
| 150 | // order rather than whichever one a sort happened to move first. | ||
| 151 | // | ||
| 148 | 152 | // Whole namespaces rather than files: a Go module cache with one file missing is | |
| 149 | 153 | // worse than no Go module cache, because the failure is a checksum error deep in a | |
| 150 | 154 | // build rather than a download. | |
| @@ -171,7 +175,12 @@ func Trim(root string, maxBytes int64) (int64, error) { | |||
| 171 | 175 | if total <= maxBytes { | |
| 172 | 176 | return 0, nil | |
| 173 | 177 | } | |
| 174 | sort.Slice(all, func(i, j int) bool { return all[i].newest.Before(all[j].newest) }) | ||
| 178 | sort.Slice(all, func(i, j int) bool { | ||
| 179 | if all[i].newest.Equal(all[j].newest) { | ||
| 180 | return all[i].path < all[j].path | ||
| 181 | } | ||
| 182 | return all[i].newest.Before(all[j].newest) | ||
| 183 | }) | ||
| 175 | 184 | ||
| 176 | 185 | var removed int64 | |
| 177 | 186 | for _, ns := range all { | |
| @@ -269,7 +278,15 @@ func dirs(p string) []string { | |||
| 269 | 278 | return out | |
| 270 | 279 | } | |
| 271 | 280 | ||
| 281 | // measure returns a namespace's size and when it was last written. Only files | ||
| 282 | // count as writes: a directory's mtime moves when a tree is unpacked, so counting | ||
| 283 | // it would age a namespace by the act of extracting it rather than by the work it | ||
| 284 | // holds — and two namespaces created in the same breath would tie, after which the | ||
| 285 | // eviction order was whatever the sort did with equal keys. An empty namespace has | ||
| 286 | // no file to date it, so it falls back to its own mtime and can still be aged | ||
| 287 | // against the others. | ||
| 272 | 288 | func measure(dir string) (size int64, newest time.Time) { | |
| 289 | var created time.Time | ||
| 273 | 290 | _ = filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error { | |
| 274 | 291 | if err != nil { | |
| 275 | 292 | return nil | |
| @@ -278,14 +295,21 @@ func measure(dir string) (size int64, newest time.Time) { | |||
| 278 | 295 | if err != nil { | |
| 279 | 296 | return nil | |
| 280 | 297 | } | |
| 298 | if d.IsDir() { | ||
| 299 | if p == dir { | ||
| 300 | created = info.ModTime() | ||
| 301 | } | ||
| 302 | return nil | ||
| 303 | } | ||
| 304 | size += info.Size() | ||
| 281 | 305 | if info.ModTime().After(newest) { | |
| 282 | 306 | newest = info.ModTime() | |
| 283 | 307 | } | |
| 284 | if !d.IsDir() { | ||
| 285 | size += info.Size() | ||
| 286 | } | ||
| 287 | 308 | return nil | |
| 288 | 309 | }) | |
| 310 | if newest.IsZero() { | ||
| 311 | newest = created | ||
| 312 | } | ||
| 289 | 313 | return size, newest | |
| 290 | 314 | } | |
| 291 | 315 | ||