#185·go-cache

Using testing/synctest on cache with janitor goroutine will hang forever

Author: cyrilc-proCreated Feb 27, 2025Updated Mar 18, 2025

When using the new experimental synctest feature of Go 1.24, I observe the following.

This test runs just fine:

go
func TestCacheWithoutJanitor(t *testing.T) {
	synctest.Run(func() {
		c := cache.New(time.Second, cache.NoExpiration)
		c.Set("foo", "bar", cache.DefaultExpiration)
	})
}

This test hangs forever:

go
func TestCacheWithJanitor(t *testing.T) {
	synctest.Run(func() {
		c := cache.New(time.Second, time.Minute)
		c.Set("foo", "bar", cache.DefaultExpiration)
	})
}

The incompatibility comes from:

  • synctest.Run will wait for all goroutines to exit (including the janitor goroutine)
  • cache uses runtime.SetFinalizer to stop the janitor goroutine but is never called

Forcing the GC to run produces a panic send on synctest channel from outside bubble:

go
func TestCacheWithJanitor(t *testing.T) {
	synctest.Run(func() {
		c := cache.New(time.Second, time.Minute)
		c.Set("foo", "bar", cache.DefaultExpiration)
		c = nil
		runtime.GC()
	})
}

My suggestion would be to provide a Close() function on the cache object to stop the janitor goroutine without the need for the GC to run.