Skip to content

Commit 1fa46c2

Browse files
authored
Allow subsequent calls to Start and Stop (#177)
Added a new flag to support calling stopped cache cleanup procedures as well as a mutex to control it. Now: - Start method will only run once even with the subsequent calls to it. - Stop will never block, in case the Start was not called or Stop was called before, it will return as the cache cleanup process is not running. Closes #175
1 parent 999c0b9 commit 1fa46c2

2 files changed

Lines changed: 49 additions & 4 deletions

File tree

cache.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,18 @@ type Cache[K comparable, V any] struct {
5555
}
5656
}
5757

58+
stopMu sync.Mutex
5859
stopCh chan struct{}
60+
stopped bool
61+
5962
options options[K, V]
6063
}
6164

6265
// New creates a new instance of cache.
6366
func New[K comparable, V any](opts ...Option[K, V]) *Cache[K, V] {
6467
c := &Cache[K, V]{
65-
stopCh: make(chan struct{}),
68+
stopCh: make(chan struct{}),
69+
stopped: true, // cache cleanup process is stopped by default
6670
}
6771
c.items.values = make(map[K]*list.Element)
6872
c.items.lru = list.New()
@@ -621,6 +625,15 @@ func (c *Cache[K, V]) Metrics() Metrics {
621625
// expired items.
622626
// It blocks until Stop is called.
623627
func (c *Cache[K, V]) Start() {
628+
c.stopMu.Lock()
629+
if !c.stopped {
630+
c.stopMu.Unlock()
631+
return
632+
}
633+
634+
c.stopped = false
635+
c.stopMu.Unlock()
636+
624637
waitDur := func() time.Duration {
625638
c.items.mu.RLock()
626639
defer c.items.mu.RUnlock()
@@ -674,7 +687,16 @@ func (c *Cache[K, V]) Start() {
674687
// Stop stops the automatic cleanup process.
675688
// It blocks until the cleanup process exits.
676689
func (c *Cache[K, V]) Stop() {
690+
c.stopMu.Lock()
691+
defer c.stopMu.Unlock()
692+
693+
if c.stopped {
694+
return
695+
}
696+
677697
c.stopCh <- struct{}{}
698+
c.stopped = true
699+
678700
}
679701

680702
// OnInsertion adds the provided function to be executed when

cache_test.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,13 +1029,34 @@ func Test_Cache_Start(t *testing.T) {
10291029
cache.events.eviction.fns[1] = fn
10301030

10311031
cache.Start()
1032+
1033+
cache.events.eviction.fns = make(map[uint64]func(EvictionReason, *Item[string, string]))
1034+
cache.stopCh = make(chan struct{})
1035+
cache.stopped = true
1036+
1037+
go cache.Start()
1038+
go cache.Start() // should be no-op
1039+
1040+
assert.Eventually(t, func() bool {
1041+
cache.stopMu.Lock()
1042+
defer cache.stopMu.Unlock()
1043+
return !cache.stopped
1044+
}, time.Second, time.Millisecond*100)
1045+
1046+
assert.NotPanics(t, cache.Stop)
1047+
10321048
}
10331049

10341050
func Test_Cache_Stop(t *testing.T) {
10351051
cache := Cache[string, string]{
1036-
stopCh: make(chan struct{}, 1),
1052+
stopCh: make(chan struct{}, 1),
1053+
stopped: true,
10371054
}
10381055
cache.Stop()
1056+
assert.Len(t, cache.stopCh, 0)
1057+
1058+
cache.stopped = false
1059+
cache.Stop()
10391060
assert.Len(t, cache.stopCh, 1)
10401061
}
10411062

@@ -1333,15 +1354,17 @@ func Test_SuppressedLoader_Load(t *testing.T) {
13331354
}
13341355

13351356
func prepCache(maxCost uint64, ttl time.Duration, keys ...string) *Cache[string, string] {
1336-
c := &Cache[string, string]{}
1357+
c := &Cache[string, string]{
1358+
stopped: true,
1359+
}
13371360
c.options.ttl = ttl
13381361
c.options.itemOpts = append(c.options.itemOpts,
13391362
withVersionTracking[string, string](false))
13401363

13411364
if maxCost != 0 {
13421365
c.options.maxCost = maxCost
13431366
c.options.itemOpts = append(c.options.itemOpts,
1344-
withCostFunc[string, string](func(item *Item[string, string]) uint64 {
1367+
withCostFunc(func(item *Item[string, string]) uint64 {
13451368
return uint64(len(item.value))
13461369
}))
13471370
}

0 commit comments

Comments
 (0)