From 884405c431ca1694a5a5cb4bf537b047ef6e3104 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Thu, 1 Sep 2016 14:03:22 +0800 Subject: [PATCH] Add namespace for Item and inline janitor Signed-off-by: Peng Gao --- cache.go | 151 +++++++++++++++++++------------------------ cachemap/cache.tmpl | 152 ++++++++++++++++++++------------------------ cachemap/const.tmpl | 2 - sharded.go | 12 ++-- 4 files changed, 141 insertions(+), 176 deletions(-) diff --git a/cache.go b/cache.go index 83ca900..63183dd 100644 --- a/cache.go +++ b/cache.go @@ -22,34 +22,34 @@ type Attr_tpl struct { } // Item struct -type Item struct { +type Item_tpl struct { Object ValueType_tpl Expiration int64 } // Expired returns true if the item has expired. -func (item Item) Expired() bool { +func (item Item_tpl) Expired() bool { return item.Expiration != 0 && time.Now().UnixNano() > item.Expiration } // Cache struct type Cache_tpl struct { - *cache + *cache_tpl // If this is confusing, see the comment at the bottom of New() } -type cache struct { +type cache_tpl struct { defaultExpiration time.Duration - items map[string]Item + items map[string]Item_tpl mu sync.RWMutex onEvicted func(string, ValueType_tpl) - janitor *janitor + stop chan bool } // Add an item to the cache, replacing any existing item. If the duration is 0 // (DefaultExpiration), the cache's default expiration time is used. If it is -1 // (NoExpiration), the item never expires. -func (c *cache) Set(k string, x ValueType_tpl, d time.Duration) { +func (c *cache_tpl) Set(k string, x ValueType_tpl, d time.Duration) { // "Inlining" of set var e int64 if d == DefaultExpiration { @@ -59,7 +59,7 @@ func (c *cache) Set(k string, x ValueType_tpl, d time.Duration) { e = time.Now().Add(d).UnixNano() } c.mu.Lock() - c.items[k] = Item{ + c.items[k] = Item_tpl{ Object: x, Expiration: e, } @@ -68,7 +68,7 @@ func (c *cache) Set(k string, x ValueType_tpl, d time.Duration) { c.mu.Unlock() } -func (c *cache) set(k string, x ValueType_tpl, d time.Duration) { +func (c *cache_tpl) set(k string, x ValueType_tpl, d time.Duration) { var e int64 if d == DefaultExpiration { d = c.defaultExpiration @@ -76,7 +76,7 @@ func (c *cache) set(k string, x ValueType_tpl, d time.Duration) { if d > 0 { e = time.Now().Add(d).UnixNano() } - c.items[k] = Item{ + c.items[k] = Item_tpl{ Object: x, Expiration: e, } @@ -84,7 +84,7 @@ func (c *cache) set(k string, x ValueType_tpl, d time.Duration) { // Add an item to the cache only if an item doesn't already exist for the given // key, or if the existing item has expired. Returns an error otherwise. -func (c *cache) Add(k string, x ValueType_tpl, d time.Duration) error { +func (c *cache_tpl) Add(k string, x ValueType_tpl, d time.Duration) error { c.mu.Lock() _, found := c.get(k) if found { @@ -98,7 +98,7 @@ func (c *cache) Add(k string, x ValueType_tpl, d time.Duration) error { // Set a new value for the cache key only if it already exists, and the existing // item hasn't expired. Returns an error otherwise. -func (c *cache) Replace(k string, x ValueType_tpl, d time.Duration) error { +func (c *cache_tpl) Replace(k string, x ValueType_tpl, d time.Duration) error { c.mu.Lock() _, found := c.get(k) if !found { @@ -112,7 +112,7 @@ func (c *cache) Replace(k string, x ValueType_tpl, d time.Duration) error { // Get an item from the cache. Returns the item or nil, and a bool indicating // whether the key was found. -func (c *cache) Get(k string) (ValueType_tpl, bool) { +func (c *cache_tpl) Get(k string) (ValueType_tpl, bool) { c.mu.RLock() // "Inlining" of get and Expired item, found := c.items[k] @@ -125,7 +125,7 @@ func (c *cache) Get(k string) (ValueType_tpl, bool) { return item.Object, true } -func (c *cache) get(k string) (*ValueType_tpl, bool) { +func (c *cache_tpl) get(k string) (*ValueType_tpl, bool) { item, found := c.items[k] if !found || item.Expiration > 0 && time.Now().UnixNano() > item.Expiration { return nil, false @@ -140,7 +140,7 @@ func (c *cache) get(k string) (*ValueType_tpl, bool) { // item's value is not an integer, if it was not found, or if it is not // possible to increment it by n. To retrieve the incremented value, use one // of the specialized methods, e.g. IncrementInt64. -func (c *cache) Increment(k string, n ValueType_tpl) error { +func (c *cache_tpl) Increment(k string, n ValueType_tpl) error { c.mu.Lock() v, found := c.items[k] if !found || v.Expired() { @@ -158,7 +158,7 @@ func (c *cache) Increment(k string, n ValueType_tpl) error { // item's value is not an integer, if it was not found, or if it is not // possible to decrement it by n. To retrieve the decremented value, use one // of the specialized methods, e.g. DecrementInt64. -func (c *cache) Decrement(k string, n ValueType_tpl) error { +func (c *cache_tpl) Decrement(k string, n ValueType_tpl) error { // TODO: Implement Increment and Decrement more cleanly. // (Cannot do Increment(k, n*-1) for uints.) c.mu.Lock() @@ -176,7 +176,7 @@ func (c *cache) Decrement(k string, n ValueType_tpl) error { // MARK_Numberic_tpl_end // Delete an item from the cache. Does nothing if the key is not in the cache. -func (c *cache) Delete(k string) { +func (c *cache_tpl) Delete(k string) { // fast path if c.onEvicted == nil { c.mu.Lock() @@ -193,7 +193,7 @@ func (c *cache) Delete(k string) { } } -func (c *cache) delete(k string) (ValueType_tpl, bool) { +func (c *cache_tpl) delete(k string) (ValueType_tpl, bool) { if v, found := c.items[k]; found { delete(c.items, k) return v.Object, true @@ -202,18 +202,16 @@ func (c *cache) delete(k string) (ValueType_tpl, bool) { return 0, false } -func (c *cache) deleteFast(k string) { +func (c *cache_tpl) deleteFast(k string) { delete(c.items, k) } -type keyAndValue struct { - key string - value ValueType_tpl -} - // Delete all expired items from the cache. -func (c *cache) DeleteExpired() { - var evictedItems []keyAndValue +func (c *cache_tpl) DeleteExpired() { + var evictedItems []struct { + key string + value ValueType_tpl + } now := time.Now().UnixNano() // fast path if c.onEvicted == nil { @@ -234,7 +232,10 @@ func (c *cache) DeleteExpired() { if v.Expiration > 0 && now > v.Expiration { ov, evicted := c.delete(k) if evicted { - evictedItems = append(evictedItems, keyAndValue{k, ov}) + evictedItems = append(evictedItems, struct { + key string + value ValueType_tpl + }{k, ov}) } } } @@ -246,7 +247,7 @@ func (c *cache) DeleteExpired() { // Returns the number of items in the cache. This may include items that have // expired, but have not yet been cleaned up. Equivalent to len(c.Items()). -func (c *cache) ItemCount() int { +func (c *cache_tpl) ItemCount() int { c.mu.RLock() n := len(c.items) c.mu.RUnlock() @@ -254,68 +255,32 @@ func (c *cache) ItemCount() int { } // Delete all items from the cache. -func (c *cache) Flush() { +func (c *cache_tpl) Flush() { c.mu.Lock() - c.items = map[string]Item{} + c.items = map[string]Item_tpl{} c.mu.Unlock() } -type janitor struct { - Interval time.Duration - stop chan bool +// _ *cache_tpl is used as a namespace, so that "stopJanitor" will not conflicts for the name. +func (_ *cache_tpl) stopJanitor(c *Cache_tpl) { + c.stop <- true } -func (j *janitor) Run(c *cache) { - j.stop = make(chan bool) - ticker := time.NewTicker(j.Interval) - for { - select { - case <-ticker.C: - c.DeleteExpired() - case <-j.stop: - ticker.Stop() - return +func (c *cache_tpl) runJanitor(ci time.Duration) { + c.stop = make(chan bool) + go func() { + ticker := time.NewTicker(ci) + for { + select { + case <-ticker.C: + c.DeleteExpired() + case <-c.stop: + ticker.Stop() + return + } } - } -} + }() -func stopJanitor(c *Cache_tpl) { - c.janitor.stop <- true -} - -func runJanitor(c *cache, ci time.Duration) { - j := &janitor{ - Interval: ci, - } - c.janitor = j - go j.Run(c) -} - -func newCache(de time.Duration, m map[string]Item) *cache { - if de == 0 { - de = -1 - } - c := &cache{ - defaultExpiration: de, - items: m, - } - return c -} - -func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item, onEvicted func(k string, v ValueType_tpl)) *Cache_tpl { - c := newCache(de, m) - c.onEvicted = onEvicted - // This trick ensures that the janitor goroutine (which--granted it - // was enabled--is running DeleteExpired on c forever) does not keep - // the returned C object from being garbage collected. When it is - // garbage collected, the finalizer stops the janitor goroutine, after - // which c can be collected. - C := &Cache_tpl{c} - if ci > 0 { - runJanitor(c, ci) - runtime.SetFinalizer(C, stopJanitor) - } - return C } // New Returns a new cache with a given default expiration duration and @@ -325,6 +290,24 @@ func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item, // expired items are not deleted from the cache before calling c.DeleteExpired(). // func New_tpl(attr Attr_tpl) *Cache_tpl { - items := make(map[string]Item, attr.Size) - return newCacheWithJanitor(attr.DefaultExpiration, attr.DefaultCleanupInterval, items, attr.OnEvicted) + items := make(map[string]Item_tpl, attr.Size) + if attr.DefaultExpiration == 0 { + attr.DefaultExpiration = -1 + } + c := &cache_tpl{ + defaultExpiration: attr.DefaultExpiration, + items: items, + } + c.onEvicted = attr.OnEvicted + // This trick ensures that the janitor goroutine (which--granted it + // was enabled--is running DeleteExpired on c forever) does not keep + // the returned C object from being garbage collected. When it is + // garbage collected, the finalizer stops the janitor goroutine, after + // which c can be collected. + C := &Cache_tpl{c} + if attr.DefaultCleanupInterval > 0 { + c.runJanitor(attr.DefaultCleanupInterval) + runtime.SetFinalizer(C, c.stopJanitor) + } + return C } diff --git a/cachemap/cache.tmpl b/cachemap/cache.tmpl index a7ec550..382fa83 100644 --- a/cachemap/cache.tmpl +++ b/cachemap/cache.tmpl @@ -22,34 +22,34 @@ type {{.ValueType}}CacheAttr struct { } // Item struct -type Item struct { +type {{.ValueType}}Item struct { Object {{.ValueType}} Expiration int64 } // Expired returns true if the item has expired. -func (item Item) Expired() bool { +func (item {{.ValueType}}Item) Expired() bool { return item.Expiration != 0 && time.Now().UnixNano() > item.Expiration } // Cache struct type {{.ValueType}}Cache struct { - *cache + *{{.ValueType}}cache // If this is confusing, see the comment at the bottom of New() } -type cache struct { +type {{.ValueType}}cache struct { defaultExpiration time.Duration - items map[string]Item + items map[string]{{.ValueType}}Item mu sync.RWMutex onEvicted func(string, {{.ValueType}}) - janitor *janitor + stop chan bool } // Add an item to the cache, replacing any existing item. If the duration is 0 // (DefaultExpiration), the cache's default expiration time is used. If it is -1 // (NoExpiration), the item never expires. -func (c *cache) Set(k string, x {{.ValueType}}, d time.Duration) { +func (c *{{.ValueType}}cache) Set(k string, x {{.ValueType}}, d time.Duration) { // "Inlining" of set var e int64 if d == DefaultExpiration { @@ -59,7 +59,7 @@ func (c *cache) Set(k string, x {{.ValueType}}, d time.Duration) { e = time.Now().Add(d).UnixNano() } c.mu.Lock() - c.items[k] = Item{ + c.items[k] = {{.ValueType}}Item{ Object: x, Expiration: e, } @@ -68,7 +68,7 @@ func (c *cache) Set(k string, x {{.ValueType}}, d time.Duration) { c.mu.Unlock() } -func (c *cache) set(k string, x {{.ValueType}}, d time.Duration) { +func (c *{{.ValueType}}cache) set(k string, x {{.ValueType}}, d time.Duration) { var e int64 if d == DefaultExpiration { d = c.defaultExpiration @@ -76,7 +76,7 @@ func (c *cache) set(k string, x {{.ValueType}}, d time.Duration) { if d > 0 { e = time.Now().Add(d).UnixNano() } - c.items[k] = Item{ + c.items[k] = {{.ValueType}}Item{ Object: x, Expiration: e, } @@ -84,7 +84,7 @@ func (c *cache) set(k string, x {{.ValueType}}, d time.Duration) { // Add an item to the cache only if an item doesn't already exist for the given // key, or if the existing item has expired. Returns an error otherwise. -func (c *cache) Add(k string, x {{.ValueType}}, d time.Duration) error { +func (c *{{.ValueType}}cache) Add(k string, x {{.ValueType}}, d time.Duration) error { c.mu.Lock() _, found := c.get(k) if found { @@ -98,7 +98,7 @@ func (c *cache) Add(k string, x {{.ValueType}}, d time.Duration) error { // Set a new value for the cache key only if it already exists, and the existing // item hasn't expired. Returns an error otherwise. -func (c *cache) Replace(k string, x {{.ValueType}}, d time.Duration) error { +func (c *{{.ValueType}}cache) Replace(k string, x {{.ValueType}}, d time.Duration) error { c.mu.Lock() _, found := c.get(k) if !found { @@ -112,7 +112,7 @@ func (c *cache) Replace(k string, x {{.ValueType}}, d time.Duration) error { // Get an item from the cache. Returns the item or nil, and a bool indicating // whether the key was found. -func (c *cache) Get(k string) ({{.ValueType}}, bool) { +func (c *{{.ValueType}}cache) Get(k string) ({{.ValueType}}, bool) { c.mu.RLock() // "Inlining" of get and Expired item, found := c.items[k] @@ -125,7 +125,7 @@ func (c *cache) Get(k string) ({{.ValueType}}, bool) { return item.Object, true } -func (c *cache) get(k string) (*{{.ValueType}}, bool) { +func (c *{{.ValueType}}cache) get(k string) (*{{.ValueType}}, bool) { item, found := c.items[k] if !found || item.Expiration > 0 && time.Now().UnixNano() > item.Expiration { return nil, false @@ -140,7 +140,7 @@ func (c *cache) get(k string) (*{{.ValueType}}, bool) { // item's value is not an integer, if it was not found, or if it is not // possible to increment it by n. To retrieve the incremented value, use one // of the specialized methods, e.g. IncrementInt64. -func (c *cache) Increment(k string, n {{.ValueType}}) error { +func (c *{{.ValueType}}cache) Increment(k string, n {{.ValueType}}) error { c.mu.Lock() v, found := c.items[k] if !found || v.Expired() { @@ -158,7 +158,7 @@ func (c *cache) Increment(k string, n {{.ValueType}}) error { // item's value is not an integer, if it was not found, or if it is not // possible to decrement it by n. To retrieve the decremented value, use one // of the specialized methods, e.g. DecrementInt64. -func (c *cache) Decrement(k string, n {{.ValueType}}) error { +func (c *{{.ValueType}}cache) Decrement(k string, n {{.ValueType}}) error { // TODO: Implement Increment and Decrement more cleanly. // (Cannot do Increment(k, n*-1) for uints.) c.mu.Lock() @@ -167,6 +167,7 @@ func (c *cache) Decrement(k string, n {{.ValueType}}) error { c.mu.Unlock() return fmt.Errorf("Item not found") } + v.Object -= n c.items[k] = v c.mu.Unlock() return nil @@ -175,7 +176,7 @@ func (c *cache) Decrement(k string, n {{.ValueType}}) error { {{end}} // Delete an item from the cache. Does nothing if the key is not in the cache. -func (c *cache) Delete(k string) { +func (c *{{.ValueType}}cache) Delete(k string) { // fast path if c.onEvicted == nil { c.mu.Lock() @@ -192,7 +193,7 @@ func (c *cache) Delete(k string) { } } -func (c *cache) delete(k string) ({{.ValueType}}, bool) { +func (c *{{.ValueType}}cache) delete(k string) ({{.ValueType}}, bool) { if v, found := c.items[k]; found { delete(c.items, k) return v.Object, true @@ -200,18 +201,16 @@ func (c *cache) delete(k string) ({{.ValueType}}, bool) { return {{.ZeroValue}}, false } -func (c *cache) deleteFast(k string) { +func (c *{{.ValueType}}cache) deleteFast(k string) { delete(c.items, k) } -type keyAndValue struct { - key string - value {{.ValueType}} -} - // Delete all expired items from the cache. -func (c *cache) DeleteExpired() { - var evictedItems []keyAndValue +func (c *{{.ValueType}}cache) DeleteExpired() { + var evictedItems []struct{ + key string + value {{.ValueType}} + } now := time.Now().UnixNano() // fast path if c.onEvicted == nil { @@ -232,7 +231,10 @@ func (c *cache) DeleteExpired() { if v.Expiration > 0 && now > v.Expiration { ov, evicted := c.delete(k) if evicted { - evictedItems = append(evictedItems, keyAndValue{k, ov}) + evictedItems = append(evictedItems, struct{ + key string + value {{.ValueType}} + }{k, ov}) } } } @@ -244,7 +246,7 @@ func (c *cache) DeleteExpired() { // Returns the number of items in the cache. This may include items that have // expired, but have not yet been cleaned up. Equivalent to len(c.Items()). -func (c *cache) ItemCount() int { +func (c *{{.ValueType}}cache) ItemCount() int { c.mu.RLock() n := len(c.items) c.mu.RUnlock() @@ -252,68 +254,32 @@ func (c *cache) ItemCount() int { } // Delete all items from the cache. -func (c *cache) Flush() { +func (c *{{.ValueType}}cache) Flush() { c.mu.Lock() - c.items = map[string]Item{} + c.items = map[string]{{.ValueType}}Item{} c.mu.Unlock() } -type janitor struct { - Interval time.Duration - stop chan bool +// _ *{{.ValueType}}cache is used as a namespace, so that "stopJanitor" will not conflicts for the name. +func (_ *{{.ValueType}}cache) stopJanitor(c *{{.ValueType}}Cache) { + c.stop <- true } -func (j *janitor) Run(c *cache) { - j.stop = make(chan bool) - ticker := time.NewTicker(j.Interval) - for { - select { - case <-ticker.C: - c.DeleteExpired() - case <-j.stop: - ticker.Stop() - return +func (c *{{.ValueType}}cache) runJanitor(ci time.Duration) { + c.stop = make(chan bool) + go func() { + ticker := time.NewTicker(ci) + for { + select { + case <-ticker.C: + c.DeleteExpired() + case <-c.stop: + ticker.Stop() + return + } } - } -} + }() -func stopJanitor(c *{{.ValueType}}Cache) { - c.janitor.stop <- true -} - -func runJanitor(c *cache, ci time.Duration) { - j := &janitor{ - Interval: ci, - } - c.janitor = j - go j.Run(c) -} - -func newCache(de time.Duration, m map[string]Item) *cache { - if de == 0 { - de = -1 - } - c := &cache{ - defaultExpiration: de, - items: m, - } - return c -} - -func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item, onEvicted func(k string, v {{.ValueType}})) *{{.ValueType}}Cache { - c := newCache(de, m) - c.onEvicted = onEvicted - // This trick ensures that the janitor goroutine (which--granted it - // was enabled--is running DeleteExpired on c forever) does not keep - // the returned C object from being garbage collected. When it is - // garbage collected, the finalizer stops the janitor goroutine, after - // which c can be collected. - C := &{{.ValueType}}Cache{c} - if ci > 0 { - runJanitor(c, ci) - runtime.SetFinalizer(C, stopJanitor) - } - return C } // New Returns a new cache with a given default expiration duration and @@ -323,6 +289,24 @@ func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item, // expired items are not deleted from the cache before calling c.DeleteExpired(). // func New{{.ValueType}}Cache(attr {{.ValueType}}CacheAttr) *{{.ValueType}}Cache { - items := make(map[string]Item, attr.Size) - return newCacheWithJanitor(attr.DefaultExpiration, attr.DefaultCleanupInterval, items, attr.OnEvicted) + items := make(map[string]{{.ValueType}}Item, attr.Size) + if attr.DefaultExpiration == 0 { + attr.DefaultExpiration = -1 + } + c := &{{.ValueType}}cache{ + defaultExpiration: attr.DefaultExpiration, + items: items, + } + c.onEvicted = attr.OnEvicted + // This trick ensures that the janitor goroutine (which--granted it + // was enabled--is running DeleteExpired on c forever) does not keep + // the returned C object from being garbage collected. When it is + // garbage collected, the finalizer stops the janitor goroutine, after + // which c can be collected. + C := &{{.ValueType}}Cache{c} + if attr.DefaultCleanupInterval > 0 { + c.runJanitor(attr.DefaultCleanupInterval) + runtime.SetFinalizer(C, c.stopJanitor) + } + return C } diff --git a/cachemap/const.tmpl b/cachemap/const.tmpl index 8b61a4a..c635c6b 100644 --- a/cachemap/const.tmpl +++ b/cachemap/const.tmpl @@ -15,5 +15,3 @@ const ( // created (e.g. 5 minutes.) DefaultExpiration time.Duration = 0 ) - - diff --git a/sharded.go b/sharded.go index 1cd80ef..d18ffa2 100644 --- a/sharded.go +++ b/sharded.go @@ -26,7 +26,7 @@ type unexportedShardedCache struct { type shardedCache struct { seed uint32 m uint32 - cs []*cache + cs []*cache_tpl janitor *shardedJanitor } @@ -62,7 +62,7 @@ func djb33(seed uint32, k string) uint32 { return d ^ (d >> 16) } -func (sc *shardedCache) bucket(k string) *cache { +func (sc *shardedCache) bucket(k string) *cache_tpl { return sc.cs[djb33(sc.seed, k)%sc.m] } @@ -106,7 +106,7 @@ func (sc *shardedCache) DeleteExpired() { // is needed to use a cache and its corresponding Items() return values at // the same time, as the maps are shared. // TODO: 不准备暴露这个接口,使用者不应该知道底层的数据. -func (sc *shardedCache) items() []map[string]Item { +func (sc *shardedCache) items() []map[string]Item_tpl { return nil } @@ -159,12 +159,12 @@ func newShardedCache(n int, de time.Duration) *shardedCache { sc := &shardedCache{ seed: seed, m: uint32(n), - cs: make([]*cache, n), + cs: make([]*cache_tpl, n), } for i := 0; i < n; i++ { - c := &cache{ + c := &cache_tpl{ defaultExpiration: de, - items: map[string]Item{}, + items: map[string]Item_tpl{}, } sc.cs[i] = c }