Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -1087,18 +1087,43 @@ func (j *janitor) Run(c *cache) {
}

func stopJanitor(c *Cache) {
c.janitor.stop <- true
if c == nil || c.cache == nil || c.janitor == nil {
return
}
// Non-blocking send: Stop may already have closed the janitor.
select {
case c.janitor.stop <- true:
default:
}
c.janitor = nil
}

func runJanitor(c *cache, ci time.Duration) {
j := &janitor{
Interval: ci,
stop: make(chan bool),
stop: make(chan bool, 1),
}
c.janitor = j
go j.Run(c)
}

// Stop ends the background janitor goroutine, if one is running.
// It is safe to call multiple times. After Stop, expired items are no longer
// removed automatically; callers may still invoke DeleteExpired manually.
//
// Prefer Stop over relying solely on the finalizer when the cache is used in
// tests (including testing/synctest) or short-lived processes that check for
// goroutine leaks.
func (c *Cache) Stop() {
if c == nil || c.cache == nil {
return
}
if c.janitor != nil {
stopJanitor(c)
}
runtime.SetFinalizer(c, nil)
}

func newCache(de time.Duration, m map[string]Item) *cache {
if de == 0 {
de = -1
Expand Down
20 changes: 20 additions & 0 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1769,3 +1769,23 @@ func TestGetWithExpiration(t *testing.T) {
t.Error("expiration for e is in the past")
}
}


func TestStopJanitor(t *testing.T) {
tc := New(DefaultExpiration, 50*time.Millisecond)
tc.Set("a", "b", DefaultExpiration)
tc.Stop()
// Second Stop must be a no-op.
tc.Stop()
// Cache remains usable after Stop.
tc.Set("c", "d", DefaultExpiration)
v, found := tc.Get("c")
if !found || v != "d" {
t.Fatalf("expected usable cache after Stop, got %v %v", v, found)
}
}

func TestStopWithoutJanitor(t *testing.T) {
tc := New(DefaultExpiration, 0)
tc.Stop() // no-op path
}