What
Trim ordered namespaces by the newest mtime in the tree, directories included. A
directory’s mtime moves when a tree is unpacked, so a namespace whose content is old can
look fresh, and two namespaces created in the same breath compare equal — after which
sort.Slice, being unstable, picks whichever it likes.
This dates a namespace by its files, falling back to the namespace directory’s own mtime when it holds no file, and breaks an exact tie by path so the same namespace is evicted on every pass.
Why
#236. The test step of verify failed on a runner for run
01M6YSX585GD5BDT45H0FE65AE with the oldest namespace survived: <nil>, while Trim had
reported removing 1000 bytes with no error — so it evicted the newer namespace. The same
code and test passed on ext4, and the ordering key was the only thing that could differ.
Tests
TestTrimOrdersByContentNotDirectories(new): a namespace whose content is two hours old but whose directory was touched just now must still be the one evicted. Fails on the previous implementation, with the message the runner printed.TestTrimBreaksATieDeterministically(new): two namespaces with identical timestamps, twenty fresh runs, the same one goes every time.TestTrimDropsTheOldestNamespaceWholeunchanged, and now deterministic.
What I ran
./vole workflow run verify—ok: 1 job(s)in the job image via docker: format, vet, build, licenses, govulncheck, packages, deployment files, markdown links, and the race test suite../vole workflow check—ok: 2 workflow(s).go test ./ci/cache -count=30andgo test -race ./ci/cache -count=5— ok.
Risk
One behaviour change: a namespace that gains directories without new files is now aged by its content, so it can be evicted sooner than before. That is the intent — directory mtimes come from unpacking, not from use — but it does change what the cap evicts first.
The silent skip of a namespace that cannot be locked is left as it was: that is deliberate (the next pass can have it) and is not what produced this failure.