vfs/vfscache: fix hang when the cache cleaner is disabled

KickCleaner sets the out of space flag, kicks the cleaner and then waits for
that flag to clear. Only the cleaner clears it, and the cleaner returns
immediately when the cache poll interval is not positive, so when it is
disabled nothing ever reads the kick or clears the flag and the caller waits
forever.

It now returns straight away in that case, under the same condition the
cleaner itself uses to decide it is disabled. Callers already retry a bounded
number of times and then report the error, which is the right outcome when
nothing is going to free space.
This commit is contained in:
ferrumclaudepilgrim
2026-09-08 16:35:36 +01:00
committed by Nick Craig-Wood
parent ca41db095b
commit e724790620
2 changed files with 36 additions and 0 deletions
+8
View File
@@ -563,7 +563,15 @@ func (c *Cache) reload(ctx context.Context) error {
}
// KickCleaner kicks cache cleaner upon out of space situation
//
// This does nothing when the cleaner is disabled. Only the cleaner clears the
// out of space condition, so with no cleaner running the wait below would never
// return.
func (c *Cache) KickCleaner() {
if c.opt.CachePollInterval <= 0 {
return
}
/* Use a separate kicker mutex for the kick to go through without waiting for the
cache mutex to avoid letting a thread kick again after the clearer just
finished cleaning and unlock the cache mutex. */
+28
View File
@@ -655,6 +655,34 @@ func TestCacheCleaner(t *testing.T) {
assert.False(t, found)
}
func TestCacheKickCleaner(t *testing.T) {
// kickCleaner runs KickCleaner and reports whether it returned in time.
kickCleaner := func(t *testing.T, c *Cache) bool {
t.Helper()
done := make(chan struct{})
go func() {
defer close(done)
c.KickCleaner()
}()
select {
case <-done:
return true
case <-time.After(2 * time.Second):
return false
}
}
// Only the cleaner clears the out of space condition, so with no cleaner
// running a KickCleaner which waited for it would never return.
t.Run("CleanerDisabled", func(t *testing.T) {
opt := vfscommon.Opt
opt.CachePollInterval = 0
_, c := newTestCacheOpt(t, opt)
assert.True(t, kickCleaner(t, c), "KickCleaner did not return with the cleaner disabled")
})
}
func TestCacheSetModTime(t *testing.T) {
_, c := newTestCache(t)