From 399b3884e24eebc68fc126dedcad6de4d6770f40 Mon Sep 17 00:00:00 2001 From: Stephen Dycus Date: Fri, 6 Mar 2015 22:00:23 -0500 Subject: [PATCH 1/2] Fixed a leaked go routine by adding a Close() function. --- cache.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/cache.go b/cache.go index dcee7fd..f0b62d3 100644 --- a/cache.go +++ b/cache.go @@ -7,9 +7,10 @@ import ( // Cache is a synchronised map of items that auto-expire once stale type Cache struct { - mutex sync.RWMutex - ttl time.Duration - items map[string]*Item + mutex sync.RWMutex + ttl time.Duration + items map[string]*Item + shutdown chan bool } // Set is a thread-safe way to add new items to the map @@ -47,6 +48,10 @@ func (cache *Cache) Count() int { return count } +func (cache *Cache) Close() { + cache.shutdown <- true +} + func (cache *Cache) cleanup() { cache.mutex.Lock() for key, item := range cache.items { @@ -66,6 +71,8 @@ func (cache *Cache) startCleanupTimer() { go (func() { for { select { + case <-cache.shutdown: + return case <-ticker: cache.cleanup() } @@ -76,8 +83,9 @@ func (cache *Cache) startCleanupTimer() { // NewCache is a helper to create instance of the Cache struct func NewCache(duration time.Duration) *Cache { cache := &Cache{ - ttl: duration, - items: map[string]*Item{}, + ttl: duration, + items: map[string]*Item{}, + shutdown: make(chan bool, 1), } cache.startCleanupTimer() return cache From eea36e1b934a7f8bd1d6dfb61c1802281cb9cbfd Mon Sep 17 00:00:00 2001 From: Stephen Dycus Date: Sat, 7 Mar 2015 08:54:34 -0500 Subject: [PATCH 2/2] Test updated --- cache_test.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/cache_test.go b/cache_test.go index a49a906..a02fa0d 100644 --- a/cache_test.go +++ b/cache_test.go @@ -1,14 +1,16 @@ package ttlcache import ( + "runtime" "testing" "time" ) func TestGet(t *testing.T) { cache := &Cache{ - ttl: time.Second, - items: map[string]*Item{}, + ttl: time.Second, + items: map[string]*Item{}, + shutdown: make(chan bool, 1), } data, exists := cache.Get("hello") @@ -24,12 +26,15 @@ func TestGet(t *testing.T) { if data != "world" { t.Errorf("Expected cache to return `world` for `hello`") } + + cache.Close() } func TestExpiration(t *testing.T) { cache := &Cache{ - ttl: time.Second, - items: map[string]*Item{}, + ttl: time.Second, + items: map[string]*Item{}, + shutdown: make(chan bool, 1), } cache.Set("x", "1") @@ -84,4 +89,20 @@ func TestExpiration(t *testing.T) { if count != 0 { t.Errorf("Expected cache to be empty") } + + cache.Close() +} + +func TestClose(t *testing.T) { + initial := runtime.NumGoroutine() + cache := NewCache(time.Second) + if runtime.NumGoroutine() != 1+initial { + t.Errorf("Expected the number of go routines to increase by one") + } + + cache.Close() + time.Sleep(time.Second) + if runtime.NumGoroutine() != initial { + t.Errorf("Expected the number of go routines to decrease by one") + } }