From 4606d7097ea8043a2b6003c4ab1ddb7b76618bdb Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Thu, 11 Aug 2016 11:58:16 +0800 Subject: [PATCH 01/14] cachemap: modify go-cache by using certain type Temporaliy use int as ValueType to pass the test. Signed-off-by: Peng Gao --- cache.go | 856 +---------------------------- cache_test.go | 1366 ++--------------------------------------------- sharded.go | 21 +- sharded_test.go | 8 +- valtyp.go | 3 + 5 files changed, 89 insertions(+), 2165 deletions(-) create mode 100644 valtyp.go diff --git a/cache.go b/cache.go index 3562543..213e180 100644 --- a/cache.go +++ b/cache.go @@ -1,17 +1,14 @@ package cache import ( - "encoding/gob" "fmt" - "io" - "os" "runtime" "sync" "time" ) type Item struct { - Object interface{} + Object ValueType Expiration int64 } @@ -41,14 +38,14 @@ type cache struct { defaultExpiration time.Duration items map[string]Item mu sync.RWMutex - onEvicted func(string, interface{}) + onEvicted func(string, *ValueType) janitor *janitor } // 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 interface{}, d time.Duration) { +func (c *cache) Set(k string, x ValueType, d time.Duration) { // "Inlining" of set var e int64 if d == DefaultExpiration { @@ -67,7 +64,7 @@ func (c *cache) Set(k string, x interface{}, d time.Duration) { c.mu.Unlock() } -func (c *cache) set(k string, x interface{}, d time.Duration) { +func (c *cache) set(k string, x ValueType, d time.Duration) { var e int64 if d == DefaultExpiration { d = c.defaultExpiration @@ -83,7 +80,7 @@ func (c *cache) set(k string, x interface{}, 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 interface{}, d time.Duration) error { +func (c *cache) Add(k string, x ValueType, d time.Duration) error { c.mu.Lock() _, found := c.get(k) if found { @@ -97,7 +94,7 @@ func (c *cache) Add(k string, x interface{}, 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 interface{}, d time.Duration) error { +func (c *cache) Replace(k string, x ValueType, d time.Duration) error { c.mu.Lock() _, found := c.get(k) if !found { @@ -111,25 +108,25 @@ func (c *cache) Replace(k string, x interface{}, 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) (interface{}, bool) { +func (c *cache) Get(k string) *ValueType { c.mu.RLock() // "Inlining" of get and Expired item, found := c.items[k] if !found { c.mu.RUnlock() - return nil, false + return nil } if item.Expiration > 0 { if time.Now().UnixNano() > item.Expiration { c.mu.RUnlock() - return nil, false + return nil } } c.mu.RUnlock() - return item.Object, true + return &item.Object } -func (c *cache) get(k string) (interface{}, bool) { +func (c *cache) get(k string) (*ValueType, bool) { item, found := c.items[k] if !found { return nil, false @@ -140,7 +137,7 @@ func (c *cache) get(k string) (interface{}, bool) { return nil, false } } - return item.Object, true + return &item.Object, true } // Increment an item of type int, int8, int16, int32, int64, uintptr, uint, @@ -148,723 +145,23 @@ func (c *cache) get(k string) (interface{}, 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. +// TODO: Increment for numberic type. func (c *cache) Increment(k string, n int64) error { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return fmt.Errorf("Item %s not found", k) - } - switch v.Object.(type) { - case int: - v.Object = v.Object.(int) + int(n) - case int8: - v.Object = v.Object.(int8) + int8(n) - case int16: - v.Object = v.Object.(int16) + int16(n) - case int32: - v.Object = v.Object.(int32) + int32(n) - case int64: - v.Object = v.Object.(int64) + n - case uint: - v.Object = v.Object.(uint) + uint(n) - case uintptr: - v.Object = v.Object.(uintptr) + uintptr(n) - case uint8: - v.Object = v.Object.(uint8) + uint8(n) - case uint16: - v.Object = v.Object.(uint16) + uint16(n) - case uint32: - v.Object = v.Object.(uint32) + uint32(n) - case uint64: - v.Object = v.Object.(uint64) + uint64(n) - case float32: - v.Object = v.Object.(float32) + float32(n) - case float64: - v.Object = v.Object.(float64) + float64(n) - default: - c.mu.Unlock() - return fmt.Errorf("The value for %s is not an integer", k) - } - c.items[k] = v - c.mu.Unlock() return nil } -// Increment an item of type float32 or float64 by n. Returns an error if the -// item's value is not floating point, if it was not found, or if it is not -// possible to increment it by n. Pass a negative number to decrement the -// value. To retrieve the incremented value, use one of the specialized methods, -// e.g. IncrementFloat64. -func (c *cache) IncrementFloat(k string, n float64) error { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return fmt.Errorf("Item %s not found", k) - } - switch v.Object.(type) { - case float32: - v.Object = v.Object.(float32) + float32(n) - case float64: - v.Object = v.Object.(float64) + n - default: - c.mu.Unlock() - return fmt.Errorf("The value for %s does not have type float32 or float64", k) - } - c.items[k] = v - c.mu.Unlock() - return nil -} - -// Increment an item of type int by n. Returns an error if the item's value is -// not an int, or if it was not found. If there is no error, the incremented -// value is returned. -func (c *cache) IncrementInt(k string, n int) (int, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type int8 by n. Returns an error if the item's value is -// not an int8, or if it was not found. If there is no error, the incremented -// value is returned. -func (c *cache) IncrementInt8(k string, n int8) (int8, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int8) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int8", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type int16 by n. Returns an error if the item's value is -// not an int16, or if it was not found. If there is no error, the incremented -// value is returned. -func (c *cache) IncrementInt16(k string, n int16) (int16, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int16) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int16", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type int32 by n. Returns an error if the item's value is -// not an int32, or if it was not found. If there is no error, the incremented -// value is returned. -func (c *cache) IncrementInt32(k string, n int32) (int32, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int32) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int32", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type int64 by n. Returns an error if the item's value is -// not an int64, or if it was not found. If there is no error, the incremented -// value is returned. -func (c *cache) IncrementInt64(k string, n int64) (int64, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int64) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int64", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type uint by n. Returns an error if the item's value is -// not an uint, or if it was not found. If there is no error, the incremented -// value is returned. -func (c *cache) IncrementUint(k string, n uint) (uint, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type uintptr by n. Returns an error if the item's value -// is not an uintptr, or if it was not found. If there is no error, the -// incremented value is returned. -func (c *cache) IncrementUintptr(k string, n uintptr) (uintptr, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uintptr) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uintptr", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type uint8 by n. Returns an error if the item's value -// is not an uint8, or if it was not found. If there is no error, the -// incremented value is returned. -func (c *cache) IncrementUint8(k string, n uint8) (uint8, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint8) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint8", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type uint16 by n. Returns an error if the item's value -// is not an uint16, or if it was not found. If there is no error, the -// incremented value is returned. -func (c *cache) IncrementUint16(k string, n uint16) (uint16, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint16) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint16", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type uint32 by n. Returns an error if the item's value -// is not an uint32, or if it was not found. If there is no error, the -// incremented value is returned. -func (c *cache) IncrementUint32(k string, n uint32) (uint32, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint32) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint32", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type uint64 by n. Returns an error if the item's value -// is not an uint64, or if it was not found. If there is no error, the -// incremented value is returned. -func (c *cache) IncrementUint64(k string, n uint64) (uint64, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint64) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint64", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type float32 by n. Returns an error if the item's value -// is not an float32, or if it was not found. If there is no error, the -// incremented value is returned. -func (c *cache) IncrementFloat32(k string, n float32) (float32, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(float32) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an float32", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Increment an item of type float64 by n. Returns an error if the item's value -// is not an float64, or if it was not found. If there is no error, the -// incremented value is returned. -func (c *cache) IncrementFloat64(k string, n float64) (float64, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(float64) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an float64", k) - } - nv := rv + n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - // Decrement an item of type int, int8, int16, int32, int64, uintptr, uint, // uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the // 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. +// TODO: Decrement func (c *cache) Decrement(k string, n int64) error { // TODO: Implement Increment and Decrement more cleanly. // (Cannot do Increment(k, n*-1) for uints.) - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return fmt.Errorf("Item not found") - } - switch v.Object.(type) { - case int: - v.Object = v.Object.(int) - int(n) - case int8: - v.Object = v.Object.(int8) - int8(n) - case int16: - v.Object = v.Object.(int16) - int16(n) - case int32: - v.Object = v.Object.(int32) - int32(n) - case int64: - v.Object = v.Object.(int64) - n - case uint: - v.Object = v.Object.(uint) - uint(n) - case uintptr: - v.Object = v.Object.(uintptr) - uintptr(n) - case uint8: - v.Object = v.Object.(uint8) - uint8(n) - case uint16: - v.Object = v.Object.(uint16) - uint16(n) - case uint32: - v.Object = v.Object.(uint32) - uint32(n) - case uint64: - v.Object = v.Object.(uint64) - uint64(n) - case float32: - v.Object = v.Object.(float32) - float32(n) - case float64: - v.Object = v.Object.(float64) - float64(n) - default: - c.mu.Unlock() - return fmt.Errorf("The value for %s is not an integer", k) - } - c.items[k] = v - c.mu.Unlock() return nil } -// Decrement an item of type float32 or float64 by n. Returns an error if the -// item's value is not floating point, if it was not found, or if it is not -// possible to decrement it by n. Pass a negative number to decrement the -// value. To retrieve the decremented value, use one of the specialized methods, -// e.g. DecrementFloat64. -func (c *cache) DecrementFloat(k string, n float64) error { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return fmt.Errorf("Item %s not found", k) - } - switch v.Object.(type) { - case float32: - v.Object = v.Object.(float32) - float32(n) - case float64: - v.Object = v.Object.(float64) - n - default: - c.mu.Unlock() - return fmt.Errorf("The value for %s does not have type float32 or float64", k) - } - c.items[k] = v - c.mu.Unlock() - return nil -} - -// Decrement an item of type int by n. Returns an error if the item's value is -// not an int, or if it was not found. If there is no error, the decremented -// value is returned. -func (c *cache) DecrementInt(k string, n int) (int, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type int8 by n. Returns an error if the item's value is -// not an int8, or if it was not found. If there is no error, the decremented -// value is returned. -func (c *cache) DecrementInt8(k string, n int8) (int8, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int8) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int8", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type int16 by n. Returns an error if the item's value is -// not an int16, or if it was not found. If there is no error, the decremented -// value is returned. -func (c *cache) DecrementInt16(k string, n int16) (int16, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int16) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int16", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type int32 by n. Returns an error if the item's value is -// not an int32, or if it was not found. If there is no error, the decremented -// value is returned. -func (c *cache) DecrementInt32(k string, n int32) (int32, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int32) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int32", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type int64 by n. Returns an error if the item's value is -// not an int64, or if it was not found. If there is no error, the decremented -// value is returned. -func (c *cache) DecrementInt64(k string, n int64) (int64, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(int64) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an int64", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type uint by n. Returns an error if the item's value is -// not an uint, or if it was not found. If there is no error, the decremented -// value is returned. -func (c *cache) DecrementUint(k string, n uint) (uint, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type uintptr by n. Returns an error if the item's value -// is not an uintptr, or if it was not found. If there is no error, the -// decremented value is returned. -func (c *cache) DecrementUintptr(k string, n uintptr) (uintptr, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uintptr) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uintptr", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type uint8 by n. Returns an error if the item's value is -// not an uint8, or if it was not found. If there is no error, the decremented -// value is returned. -func (c *cache) DecrementUint8(k string, n uint8) (uint8, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint8) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint8", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type uint16 by n. Returns an error if the item's value -// is not an uint16, or if it was not found. If there is no error, the -// decremented value is returned. -func (c *cache) DecrementUint16(k string, n uint16) (uint16, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint16) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint16", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type uint32 by n. Returns an error if the item's value -// is not an uint32, or if it was not found. If there is no error, the -// decremented value is returned. -func (c *cache) DecrementUint32(k string, n uint32) (uint32, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint32) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint32", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type uint64 by n. Returns an error if the item's value -// is not an uint64, or if it was not found. If there is no error, the -// decremented value is returned. -func (c *cache) DecrementUint64(k string, n uint64) (uint64, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(uint64) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an uint64", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type float32 by n. Returns an error if the item's value -// is not an float32, or if it was not found. If there is no error, the -// decremented value is returned. -func (c *cache) DecrementFloat32(k string, n float32) (float32, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(float32) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an float32", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - -// Decrement an item of type float64 by n. Returns an error if the item's value -// is not an float64, or if it was not found. If there is no error, the -// decremented value is returned. -func (c *cache) DecrementFloat64(k string, n float64) (float64, error) { - c.mu.Lock() - v, found := c.items[k] - if !found || v.Expired() { - c.mu.Unlock() - return 0, fmt.Errorf("Item %s not found", k) - } - rv, ok := v.Object.(float64) - if !ok { - c.mu.Unlock() - return 0, fmt.Errorf("The value for %s is not an float64", k) - } - nv := rv - n - v.Object = nv - c.items[k] = v - c.mu.Unlock() - return nv, nil -} - // Delete an item from the cache. Does nothing if the key is not in the cache. func (c *cache) Delete(k string) { c.mu.Lock() @@ -875,11 +172,11 @@ func (c *cache) Delete(k string) { } } -func (c *cache) delete(k string) (interface{}, bool) { +func (c *cache) delete(k string) (*ValueType, bool) { if c.onEvicted != nil { if v, found := c.items[k]; found { delete(c.items, k) - return v.Object, true + return &v.Object, true } } delete(c.items, k) @@ -888,7 +185,7 @@ func (c *cache) delete(k string) (interface{}, bool) { type keyAndValue struct { key string - value interface{} + value *ValueType } // Delete all expired items from the cache. @@ -914,101 +211,12 @@ func (c *cache) DeleteExpired() { // Sets an (optional) function that is called with the key and value when an // item is evicted from the cache. (Including when it is deleted manually, but // not when it is overwritten.) Set to nil to disable. -func (c *cache) OnEvicted(f func(string, interface{})) { +func (c *cache) OnEvicted(f func(string, *ValueType)) { c.mu.Lock() c.onEvicted = f c.mu.Unlock() } -// Write the cache's items (using Gob) to an io.Writer. -// -// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the -// documentation for NewFrom().) -func (c *cache) Save(w io.Writer) (err error) { - enc := gob.NewEncoder(w) - defer func() { - if x := recover(); x != nil { - err = fmt.Errorf("Error registering item types with Gob library") - } - }() - c.mu.RLock() - defer c.mu.RUnlock() - for _, v := range c.items { - gob.Register(v.Object) - } - err = enc.Encode(&c.items) - return -} - -// Save the cache's items to the given filename, creating the file if it -// doesn't exist, and overwriting it if it does. -// -// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the -// documentation for NewFrom().) -func (c *cache) SaveFile(fname string) error { - fp, err := os.Create(fname) - if err != nil { - return err - } - err = c.Save(fp) - if err != nil { - fp.Close() - return err - } - return fp.Close() -} - -// Add (Gob-serialized) cache items from an io.Reader, excluding any items with -// keys that already exist (and haven't expired) in the current cache. -// -// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the -// documentation for NewFrom().) -func (c *cache) Load(r io.Reader) error { - dec := gob.NewDecoder(r) - items := map[string]Item{} - err := dec.Decode(&items) - if err == nil { - c.mu.Lock() - defer c.mu.Unlock() - for k, v := range items { - ov, found := c.items[k] - if !found || ov.Expired() { - c.items[k] = v - } - } - } - return err -} - -// Load and add cache items from the given filename, excluding any items with -// keys that already exist in the current cache. -// -// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the -// documentation for NewFrom().) -func (c *cache) LoadFile(fname string) error { - fp, err := os.Open(fname) - if err != nil { - return err - } - err = c.Load(fp) - if err != nil { - fp.Close() - return err - } - return fp.Close() -} - -// Returns the items in the cache. This may include items that have expired, -// but have not yet been cleaned up. If this is significant, the Expiration -// fields of the items should be checked. Note that explicit synchronization -// is needed to use a cache and its corresponding Items() return value at -// the same time, as the map is shared. -func (c *cache) Items() map[string]Item { - c.mu.RLock() - defer c.mu.RUnlock() - return c.items -} - // 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 { @@ -1077,6 +285,9 @@ func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) C := &Cache{c} if ci > 0 { runJanitor(c, ci) + // 如果C被回收了,但是c不会,因为stopJanitor是一个一直运行的 + // goroutine对c一直有引用不会被回收,所以加一个Finalizer来停掉 + // 这个goroutine然后让c被回收. runtime.SetFinalizer(C, stopJanitor) } return C @@ -1091,28 +302,3 @@ func New(defaultExpiration, cleanupInterval time.Duration) *Cache { items := make(map[string]Item) return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) } - -// Return a new cache with a given default expiration duration and cleanup -// interval. If the expiration duration is less than one (or NoExpiration), -// the items in the cache never expire (by default), and must be deleted -// manually. If the cleanup interval is less than one, expired items are not -// deleted from the cache before calling c.DeleteExpired(). -// -// NewFrom() also accepts an items map which will serve as the underlying map -// for the cache. This is useful for starting from a deserialized cache -// (serialized using e.g. gob.Encode() on c.Items()), or passing in e.g. -// make(map[string]Item, 500) to improve startup performance when the cache -// is expected to reach a certain minimum size. -// -// Only the cache's methods synchronize access to this map, so it is not -// recommended to keep any references to the map around after creating a cache. -// If need be, the map can be accessed at a later point using c.Items() (subject -// to the same caveat.) -// -// Note regarding serialization: When using e.g. gob, make sure to -// gob.Register() the individual types stored in the cache before encoding a -// map retrieved with c.Items(), and to register those same types before -// decoding a blob containing an items map. -func NewFrom(defaultExpiration, cleanupInterval time.Duration, items map[string]Item) *Cache { - return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) -} diff --git a/cache_test.go b/cache_test.go index 6e81693..b0e077b 100644 --- a/cache_test.go +++ b/cache_test.go @@ -1,8 +1,6 @@ package cache import ( - "bytes" - "io/ioutil" "runtime" "strconv" "sync" @@ -18,58 +16,33 @@ type TestStruct struct { func TestCache(t *testing.T) { tc := New(DefaultExpiration, 0) - a, found := tc.Get("a") - if found || a != nil { + a := tc.Get("a") + if a != nil { t.Error("Getting A found value that shouldn't exist:", a) } - b, found := tc.Get("b") - if found || b != nil { + b := tc.Get("b") + if b != nil { t.Error("Getting B found value that shouldn't exist:", b) } - c, found := tc.Get("c") - if found || c != nil { + c := tc.Get("c") + if c != nil { t.Error("Getting C found value that shouldn't exist:", c) } - tc.Set("a", 1, DefaultExpiration) - tc.Set("b", "b", DefaultExpiration) - tc.Set("c", 3.5, DefaultExpiration) + tc.Set("a", ValueType(1), DefaultExpiration) - x, found := tc.Get("a") - if !found { - t.Error("a was not found while getting a2") - } + x := tc.Get("a") if x == nil { t.Error("x for a is nil") - } else if a2 := x.(int); a2+2 != 3 { + } else if a2 := int(*x); a2+2 != 3 { t.Error("a2 (which should be 1) plus 2 does not equal 3; value:", a2) } - - x, found = tc.Get("b") - if !found { - t.Error("b was not found while getting b2") - } - if x == nil { - t.Error("x for b is nil") - } else if b2 := x.(string); b2+"B" != "bB" { - t.Error("b2 (which should be b) plus B does not equal bB; value:", b2) - } - - x, found = tc.Get("c") - if !found { - t.Error("c was not found while getting c2") - } - if x == nil { - t.Error("x for c is nil") - } else if c2 := x.(float64); c2+1.2 != 4.7 { - t.Error("c2 (which should be 3.5) plus 1.2 does not equal 4.7; value:", c2) - } } func TestCacheTimes(t *testing.T) { - var found bool + var x *ValueType tc := New(50*time.Millisecond, 1*time.Millisecond) tc.Set("a", 1, DefaultExpiration) @@ -78,1047 +51,45 @@ func TestCacheTimes(t *testing.T) { tc.Set("d", 4, 70*time.Millisecond) <-time.After(25 * time.Millisecond) - _, found = tc.Get("c") - if found { - t.Error("Found c when it should have been automatically deleted") + x = tc.Get("c") + if x != nil { + t.Error("Found c when it should have been automatically deleted", *x) } <-time.After(30 * time.Millisecond) - _, found = tc.Get("a") - if found { - t.Error("Found a when it should have been automatically deleted") + x = tc.Get("a") + if x != nil { + t.Error("Found a when it should have been automatically deleted", *x) } - _, found = tc.Get("b") - if !found { + x = tc.Get("b") + if x == nil { t.Error("Did not find b even though it was set to never expire") } - _, found = tc.Get("d") - if !found { + x = tc.Get("d") + if x == nil { t.Error("Did not find d even though it was set to expire later than the default") } <-time.After(20 * time.Millisecond) - _, found = tc.Get("d") - if found { + x = tc.Get("d") + if x != nil { t.Error("Found d when it should have been automatically deleted (later than the default)") } } -func TestNewFrom(t *testing.T) { - m := map[string]Item{ - "a": Item{ - Object: 1, - Expiration: 0, - }, - "b": Item{ - Object: 2, - Expiration: 0, - }, - } - tc := NewFrom(DefaultExpiration, 0, m) - a, found := tc.Get("a") - if !found { - t.Fatal("Did not find a") - } - if a.(int) != 1 { - t.Fatal("a is not 1") - } - b, found := tc.Get("b") - if !found { - t.Fatal("Did not find b") - } - if b.(int) != 2 { - t.Fatal("b is not 2") - } -} - -func TestStorePointerToStruct(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("foo", &TestStruct{Num: 1}, DefaultExpiration) - x, found := tc.Get("foo") - if !found { - t.Fatal("*TestStruct was not found for foo") - } - foo := x.(*TestStruct) - foo.Num++ - - y, found := tc.Get("foo") - if !found { - t.Fatal("*TestStruct was not found for foo (second time)") - } - bar := y.(*TestStruct) - if bar.Num != 2 { - t.Fatal("TestStruct.Num is not 2") - } -} - +// TODO: test increment. func TestIncrementWithInt(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint", 1, DefaultExpiration) - err := tc.Increment("tint", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("tint") - if !found { - t.Error("tint was not found") - } - if x.(int) != 3 { - t.Error("tint is not 3:", x) - } -} - -func TestIncrementWithInt8(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint8", int8(1), DefaultExpiration) - err := tc.Increment("tint8", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("tint8") - if !found { - t.Error("tint8 was not found") - } - if x.(int8) != 3 { - t.Error("tint8 is not 3:", x) - } -} - -func TestIncrementWithInt16(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint16", int16(1), DefaultExpiration) - err := tc.Increment("tint16", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("tint16") - if !found { - t.Error("tint16 was not found") - } - if x.(int16) != 3 { - t.Error("tint16 is not 3:", x) - } -} - -func TestIncrementWithInt32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint32", int32(1), DefaultExpiration) - err := tc.Increment("tint32", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("tint32") - if !found { - t.Error("tint32 was not found") - } - if x.(int32) != 3 { - t.Error("tint32 is not 3:", x) - } -} - -func TestIncrementWithInt64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint64", int64(1), DefaultExpiration) - err := tc.Increment("tint64", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("tint64") - if !found { - t.Error("tint64 was not found") - } - if x.(int64) != 3 { - t.Error("tint64 is not 3:", x) - } -} - -func TestIncrementWithUint(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint", uint(1), DefaultExpiration) - err := tc.Increment("tuint", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("tuint") - if !found { - t.Error("tuint was not found") - } - if x.(uint) != 3 { - t.Error("tuint is not 3:", x) - } -} - -func TestIncrementWithUintptr(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuintptr", uintptr(1), DefaultExpiration) - err := tc.Increment("tuintptr", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - - x, found := tc.Get("tuintptr") - if !found { - t.Error("tuintptr was not found") - } - if x.(uintptr) != 3 { - t.Error("tuintptr is not 3:", x) - } -} - -func TestIncrementWithUint8(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint8", uint8(1), DefaultExpiration) - err := tc.Increment("tuint8", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("tuint8") - if !found { - t.Error("tuint8 was not found") - } - if x.(uint8) != 3 { - t.Error("tuint8 is not 3:", x) - } -} - -func TestIncrementWithUint16(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint16", uint16(1), DefaultExpiration) - err := tc.Increment("tuint16", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - - x, found := tc.Get("tuint16") - if !found { - t.Error("tuint16 was not found") - } - if x.(uint16) != 3 { - t.Error("tuint16 is not 3:", x) - } -} - -func TestIncrementWithUint32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint32", uint32(1), DefaultExpiration) - err := tc.Increment("tuint32", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("tuint32") - if !found { - t.Error("tuint32 was not found") - } - if x.(uint32) != 3 { - t.Error("tuint32 is not 3:", x) - } -} - -func TestIncrementWithUint64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint64", uint64(1), DefaultExpiration) - err := tc.Increment("tuint64", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - - x, found := tc.Get("tuint64") - if !found { - t.Error("tuint64 was not found") - } - if x.(uint64) != 3 { - t.Error("tuint64 is not 3:", x) - } -} - -func TestIncrementWithFloat32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float32", float32(1.5), DefaultExpiration) - err := tc.Increment("float32", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("float32") - if !found { - t.Error("float32 was not found") - } - if x.(float32) != 3.5 { - t.Error("float32 is not 3.5:", x) - } -} - -func TestIncrementWithFloat64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float64", float64(1.5), DefaultExpiration) - err := tc.Increment("float64", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - x, found := tc.Get("float64") - if !found { - t.Error("float64 was not found") - } - if x.(float64) != 3.5 { - t.Error("float64 is not 3.5:", x) - } -} - -func TestIncrementFloatWithFloat32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float32", float32(1.5), DefaultExpiration) - err := tc.IncrementFloat("float32", 2) - if err != nil { - t.Error("Error incrementfloating:", err) - } - x, found := tc.Get("float32") - if !found { - t.Error("float32 was not found") - } - if x.(float32) != 3.5 { - t.Error("float32 is not 3.5:", x) - } -} - -func TestIncrementFloatWithFloat64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float64", float64(1.5), DefaultExpiration) - err := tc.IncrementFloat("float64", 2) - if err != nil { - t.Error("Error incrementfloating:", err) - } - x, found := tc.Get("float64") - if !found { - t.Error("float64 was not found") - } - if x.(float64) != 3.5 { - t.Error("float64 is not 3.5:", x) - } -} - -func TestDecrementWithInt(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int", int(5), DefaultExpiration) - err := tc.Decrement("int", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("int") - if !found { - t.Error("int was not found") - } - if x.(int) != 3 { - t.Error("int is not 3:", x) - } -} - -func TestDecrementWithInt8(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int8", int8(5), DefaultExpiration) - err := tc.Decrement("int8", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("int8") - if !found { - t.Error("int8 was not found") - } - if x.(int8) != 3 { - t.Error("int8 is not 3:", x) - } -} - -func TestDecrementWithInt16(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int16", int16(5), DefaultExpiration) - err := tc.Decrement("int16", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("int16") - if !found { - t.Error("int16 was not found") - } - if x.(int16) != 3 { - t.Error("int16 is not 3:", x) - } -} - -func TestDecrementWithInt32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int32", int32(5), DefaultExpiration) - err := tc.Decrement("int32", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("int32") - if !found { - t.Error("int32 was not found") - } - if x.(int32) != 3 { - t.Error("int32 is not 3:", x) - } -} - -func TestDecrementWithInt64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int64", int64(5), DefaultExpiration) - err := tc.Decrement("int64", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("int64") - if !found { - t.Error("int64 was not found") - } - if x.(int64) != 3 { - t.Error("int64 is not 3:", x) - } -} - -func TestDecrementWithUint(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint", uint(5), DefaultExpiration) - err := tc.Decrement("uint", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("uint") - if !found { - t.Error("uint was not found") - } - if x.(uint) != 3 { - t.Error("uint is not 3:", x) - } -} - -func TestDecrementWithUintptr(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uintptr", uintptr(5), DefaultExpiration) - err := tc.Decrement("uintptr", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("uintptr") - if !found { - t.Error("uintptr was not found") - } - if x.(uintptr) != 3 { - t.Error("uintptr is not 3:", x) - } -} - -func TestDecrementWithUint8(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint8", uint8(5), DefaultExpiration) - err := tc.Decrement("uint8", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("uint8") - if !found { - t.Error("uint8 was not found") - } - if x.(uint8) != 3 { - t.Error("uint8 is not 3:", x) - } -} - -func TestDecrementWithUint16(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint16", uint16(5), DefaultExpiration) - err := tc.Decrement("uint16", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("uint16") - if !found { - t.Error("uint16 was not found") - } - if x.(uint16) != 3 { - t.Error("uint16 is not 3:", x) - } -} - -func TestDecrementWithUint32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint32", uint32(5), DefaultExpiration) - err := tc.Decrement("uint32", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("uint32") - if !found { - t.Error("uint32 was not found") - } - if x.(uint32) != 3 { - t.Error("uint32 is not 3:", x) - } -} - -func TestDecrementWithUint64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint64", uint64(5), DefaultExpiration) - err := tc.Decrement("uint64", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("uint64") - if !found { - t.Error("uint64 was not found") - } - if x.(uint64) != 3 { - t.Error("uint64 is not 3:", x) - } -} - -func TestDecrementWithFloat32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float32", float32(5.5), DefaultExpiration) - err := tc.Decrement("float32", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("float32") - if !found { - t.Error("float32 was not found") - } - if x.(float32) != 3.5 { - t.Error("float32 is not 3:", x) - } -} - -func TestDecrementWithFloat64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float64", float64(5.5), DefaultExpiration) - err := tc.Decrement("float64", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("float64") - if !found { - t.Error("float64 was not found") - } - if x.(float64) != 3.5 { - t.Error("float64 is not 3:", x) - } -} - -func TestDecrementFloatWithFloat32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float32", float32(5.5), DefaultExpiration) - err := tc.DecrementFloat("float32", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("float32") - if !found { - t.Error("float32 was not found") - } - if x.(float32) != 3.5 { - t.Error("float32 is not 3:", x) - } -} - -func TestDecrementFloatWithFloat64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float64", float64(5.5), DefaultExpiration) - err := tc.DecrementFloat("float64", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - x, found := tc.Get("float64") - if !found { - t.Error("float64 was not found") - } - if x.(float64) != 3.5 { - t.Error("float64 is not 3:", x) - } -} - -func TestIncrementInt(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint", 1, DefaultExpiration) - n, err := tc.IncrementInt("tint", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tint") - if !found { - t.Error("tint was not found") - } - if x.(int) != 3 { - t.Error("tint is not 3:", x) - } -} - -func TestIncrementInt8(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint8", int8(1), DefaultExpiration) - n, err := tc.IncrementInt8("tint8", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tint8") - if !found { - t.Error("tint8 was not found") - } - if x.(int8) != 3 { - t.Error("tint8 is not 3:", x) - } -} - -func TestIncrementInt16(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint16", int16(1), DefaultExpiration) - n, err := tc.IncrementInt16("tint16", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tint16") - if !found { - t.Error("tint16 was not found") - } - if x.(int16) != 3 { - t.Error("tint16 is not 3:", x) - } -} - -func TestIncrementInt32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint32", int32(1), DefaultExpiration) - n, err := tc.IncrementInt32("tint32", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tint32") - if !found { - t.Error("tint32 was not found") - } - if x.(int32) != 3 { - t.Error("tint32 is not 3:", x) - } -} - -func TestIncrementInt64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tint64", int64(1), DefaultExpiration) - n, err := tc.IncrementInt64("tint64", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tint64") - if !found { - t.Error("tint64 was not found") - } - if x.(int64) != 3 { - t.Error("tint64 is not 3:", x) - } -} - -func TestIncrementUint(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint", uint(1), DefaultExpiration) - n, err := tc.IncrementUint("tuint", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tuint") - if !found { - t.Error("tuint was not found") - } - if x.(uint) != 3 { - t.Error("tuint is not 3:", x) - } -} - -func TestIncrementUintptr(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuintptr", uintptr(1), DefaultExpiration) - n, err := tc.IncrementUintptr("tuintptr", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tuintptr") - if !found { - t.Error("tuintptr was not found") - } - if x.(uintptr) != 3 { - t.Error("tuintptr is not 3:", x) - } -} - -func TestIncrementUint8(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint8", uint8(1), DefaultExpiration) - n, err := tc.IncrementUint8("tuint8", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tuint8") - if !found { - t.Error("tuint8 was not found") - } - if x.(uint8) != 3 { - t.Error("tuint8 is not 3:", x) - } -} - -func TestIncrementUint16(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint16", uint16(1), DefaultExpiration) - n, err := tc.IncrementUint16("tuint16", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tuint16") - if !found { - t.Error("tuint16 was not found") - } - if x.(uint16) != 3 { - t.Error("tuint16 is not 3:", x) - } -} - -func TestIncrementUint32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint32", uint32(1), DefaultExpiration) - n, err := tc.IncrementUint32("tuint32", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tuint32") - if !found { - t.Error("tuint32 was not found") - } - if x.(uint32) != 3 { - t.Error("tuint32 is not 3:", x) - } -} - -func TestIncrementUint64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("tuint64", uint64(1), DefaultExpiration) - n, err := tc.IncrementUint64("tuint64", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("tuint64") - if !found { - t.Error("tuint64 was not found") - } - if x.(uint64) != 3 { - t.Error("tuint64 is not 3:", x) - } -} - -func TestIncrementFloat32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float32", float32(1.5), DefaultExpiration) - n, err := tc.IncrementFloat32("float32", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3.5 { - t.Error("Returned number is not 3.5:", n) - } - x, found := tc.Get("float32") - if !found { - t.Error("float32 was not found") - } - if x.(float32) != 3.5 { - t.Error("float32 is not 3.5:", x) - } -} - -func TestIncrementFloat64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float64", float64(1.5), DefaultExpiration) - n, err := tc.IncrementFloat64("float64", 2) - if err != nil { - t.Error("Error incrementing:", err) - } - if n != 3.5 { - t.Error("Returned number is not 3.5:", n) - } - x, found := tc.Get("float64") - if !found { - t.Error("float64 was not found") - } - if x.(float64) != 3.5 { - t.Error("float64 is not 3.5:", x) - } -} - -func TestDecrementInt8(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int8", int8(5), DefaultExpiration) - n, err := tc.DecrementInt8("int8", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("int8") - if !found { - t.Error("int8 was not found") - } - if x.(int8) != 3 { - t.Error("int8 is not 3:", x) - } -} - -func TestDecrementInt16(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int16", int16(5), DefaultExpiration) - n, err := tc.DecrementInt16("int16", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("int16") - if !found { - t.Error("int16 was not found") - } - if x.(int16) != 3 { - t.Error("int16 is not 3:", x) - } -} - -func TestDecrementInt32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int32", int32(5), DefaultExpiration) - n, err := tc.DecrementInt32("int32", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("int32") - if !found { - t.Error("int32 was not found") - } - if x.(int32) != 3 { - t.Error("int32 is not 3:", x) - } -} - -func TestDecrementInt64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int64", int64(5), DefaultExpiration) - n, err := tc.DecrementInt64("int64", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("int64") - if !found { - t.Error("int64 was not found") - } - if x.(int64) != 3 { - t.Error("int64 is not 3:", x) - } -} - -func TestDecrementUint(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint", uint(5), DefaultExpiration) - n, err := tc.DecrementUint("uint", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("uint") - if !found { - t.Error("uint was not found") - } - if x.(uint) != 3 { - t.Error("uint is not 3:", x) - } -} - -func TestDecrementUintptr(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uintptr", uintptr(5), DefaultExpiration) - n, err := tc.DecrementUintptr("uintptr", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("uintptr") - if !found { - t.Error("uintptr was not found") - } - if x.(uintptr) != 3 { - t.Error("uintptr is not 3:", x) - } -} - -func TestDecrementUint8(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint8", uint8(5), DefaultExpiration) - n, err := tc.DecrementUint8("uint8", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("uint8") - if !found { - t.Error("uint8 was not found") - } - if x.(uint8) != 3 { - t.Error("uint8 is not 3:", x) - } -} - -func TestDecrementUint16(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint16", uint16(5), DefaultExpiration) - n, err := tc.DecrementUint16("uint16", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("uint16") - if !found { - t.Error("uint16 was not found") - } - if x.(uint16) != 3 { - t.Error("uint16 is not 3:", x) - } -} - -func TestDecrementUint32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint32", uint32(5), DefaultExpiration) - n, err := tc.DecrementUint32("uint32", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("uint32") - if !found { - t.Error("uint32 was not found") - } - if x.(uint32) != 3 { - t.Error("uint32 is not 3:", x) - } -} - -func TestDecrementUint64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint64", uint64(5), DefaultExpiration) - n, err := tc.DecrementUint64("uint64", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("uint64") - if !found { - t.Error("uint64 was not found") - } - if x.(uint64) != 3 { - t.Error("uint64 is not 3:", x) - } -} - -func TestDecrementFloat32(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float32", float32(5), DefaultExpiration) - n, err := tc.DecrementFloat32("float32", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("float32") - if !found { - t.Error("float32 was not found") - } - if x.(float32) != 3 { - t.Error("float32 is not 3:", x) - } -} - -func TestDecrementFloat64(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("float64", float64(5), DefaultExpiration) - n, err := tc.DecrementFloat64("float64", 2) - if err != nil { - t.Error("Error decrementing:", err) - } - if n != 3 { - t.Error("Returned number is not 3:", n) - } - x, found := tc.Get("float64") - if !found { - t.Error("float64 was not found") - } - if x.(float64) != 3 { - t.Error("float64 is not 3:", x) - } } func TestAdd(t *testing.T) { tc := New(DefaultExpiration, 0) - err := tc.Add("foo", "bar", DefaultExpiration) + err := tc.Add("foo", 1, DefaultExpiration) if err != nil { t.Error("Couldn't add foo even though it shouldn't exist") } - err = tc.Add("foo", "baz", DefaultExpiration) + err = tc.Add("foo", 2, DefaultExpiration) if err == nil { t.Error("Successfully added another foo when it should have returned an error") } @@ -1126,12 +97,12 @@ func TestAdd(t *testing.T) { func TestReplace(t *testing.T) { tc := New(DefaultExpiration, 0) - err := tc.Replace("foo", "bar", DefaultExpiration) + err := tc.Replace("foo", 1, DefaultExpiration) if err == nil { t.Error("Replaced foo when it shouldn't exist") } - tc.Set("foo", "bar", DefaultExpiration) - err = tc.Replace("foo", "bar", DefaultExpiration) + tc.Set("foo", 1, DefaultExpiration) + err = tc.Replace("foo", 2, DefaultExpiration) if err != nil { t.Error("Couldn't replace existing key foo") } @@ -1139,12 +110,9 @@ func TestReplace(t *testing.T) { func TestDelete(t *testing.T) { tc := New(DefaultExpiration, 0) - tc.Set("foo", "bar", DefaultExpiration) + tc.Set("foo", 1, DefaultExpiration) tc.Delete("foo") - x, found := tc.Get("foo") - if found { - t.Error("foo was found, but it should have been deleted") - } + x := tc.Get("foo") if x != nil { t.Error("x is not nil:", x) } @@ -1152,9 +120,9 @@ func TestDelete(t *testing.T) { func TestItemCount(t *testing.T) { tc := New(DefaultExpiration, 0) - tc.Set("foo", "1", DefaultExpiration) - tc.Set("bar", "2", DefaultExpiration) - tc.Set("baz", "3", DefaultExpiration) + tc.Set("foo", 1, DefaultExpiration) + tc.Set("bar", 2, DefaultExpiration) + tc.Set("baz", 3, DefaultExpiration) if n := tc.ItemCount(); n != 3 { t.Errorf("Item count is not 3: %d", n) } @@ -1162,269 +130,42 @@ func TestItemCount(t *testing.T) { func TestFlush(t *testing.T) { tc := New(DefaultExpiration, 0) - tc.Set("foo", "bar", DefaultExpiration) - tc.Set("baz", "yes", DefaultExpiration) + tc.Set("foo", 1, DefaultExpiration) + tc.Set("baz", 2, DefaultExpiration) tc.Flush() - x, found := tc.Get("foo") - if found { - t.Error("foo was found, but it should have been deleted") - } + x := tc.Get("foo") if x != nil { t.Error("x is not nil:", x) } - x, found = tc.Get("baz") - if found { - t.Error("baz was found, but it should have been deleted") - } + x = tc.Get("baz") if x != nil { t.Error("x is not nil:", x) } } -func TestIncrementOverflowInt(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("int8", int8(127), DefaultExpiration) - err := tc.Increment("int8", 1) - if err != nil { - t.Error("Error incrementing int8:", err) - } - x, _ := tc.Get("int8") - int8 := x.(int8) - if int8 != -128 { - t.Error("int8 did not overflow as expected; value:", int8) - } - -} - -func TestIncrementOverflowUint(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint8", uint8(255), DefaultExpiration) - err := tc.Increment("uint8", 1) - if err != nil { - t.Error("Error incrementing int8:", err) - } - x, _ := tc.Get("uint8") - uint8 := x.(uint8) - if uint8 != 0 { - t.Error("uint8 did not overflow as expected; value:", uint8) - } -} - -func TestDecrementUnderflowUint(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("uint8", uint8(0), DefaultExpiration) - err := tc.Decrement("uint8", 1) - if err != nil { - t.Error("Error decrementing int8:", err) - } - x, _ := tc.Get("uint8") - uint8 := x.(uint8) - if uint8 != 255 { - t.Error("uint8 did not underflow as expected; value:", uint8) - } -} - func TestOnEvicted(t *testing.T) { tc := New(DefaultExpiration, 0) - tc.Set("foo", 3, DefaultExpiration) + tc.Set("foo", ValueType(3), DefaultExpiration) if tc.onEvicted != nil { t.Fatal("tc.onEvicted is not nil") } works := false - tc.OnEvicted(func(k string, v interface{}) { - if k == "foo" && v.(int) == 3 { + tc.OnEvicted(func(k string, v *ValueType) { + if k == "foo" && int(*v) == 3 { works = true } - tc.Set("bar", 4, DefaultExpiration) + tc.Set("bar", ValueType(4), DefaultExpiration) }) tc.Delete("foo") - x, _ := tc.Get("bar") + x := tc.Get("bar") if !works { t.Error("works bool not true") } - if x.(int) != 4 { + if int(*x) != 4 { t.Error("bar was not 4") } } -func TestCacheSerialization(t *testing.T) { - tc := New(DefaultExpiration, 0) - testFillAndSerialize(t, tc) - - // Check if gob.Register behaves properly even after multiple gob.Register - // on c.Items (many of which will be the same type) - testFillAndSerialize(t, tc) -} - -func testFillAndSerialize(t *testing.T, tc *Cache) { - tc.Set("a", "a", DefaultExpiration) - tc.Set("b", "b", DefaultExpiration) - tc.Set("c", "c", DefaultExpiration) - tc.Set("expired", "foo", 1*time.Millisecond) - tc.Set("*struct", &TestStruct{Num: 1}, DefaultExpiration) - tc.Set("[]struct", []TestStruct{ - {Num: 2}, - {Num: 3}, - }, DefaultExpiration) - tc.Set("[]*struct", []*TestStruct{ - &TestStruct{Num: 4}, - &TestStruct{Num: 5}, - }, DefaultExpiration) - tc.Set("structception", &TestStruct{ - Num: 42, - Children: []*TestStruct{ - &TestStruct{Num: 6174}, - &TestStruct{Num: 4716}, - }, - }, DefaultExpiration) - - fp := &bytes.Buffer{} - err := tc.Save(fp) - if err != nil { - t.Fatal("Couldn't save cache to fp:", err) - } - - oc := New(DefaultExpiration, 0) - err = oc.Load(fp) - if err != nil { - t.Fatal("Couldn't load cache from fp:", err) - } - - a, found := oc.Get("a") - if !found { - t.Error("a was not found") - } - if a.(string) != "a" { - t.Error("a is not a") - } - - b, found := oc.Get("b") - if !found { - t.Error("b was not found") - } - if b.(string) != "b" { - t.Error("b is not b") - } - - c, found := oc.Get("c") - if !found { - t.Error("c was not found") - } - if c.(string) != "c" { - t.Error("c is not c") - } - - <-time.After(5 * time.Millisecond) - _, found = oc.Get("expired") - if found { - t.Error("expired was found") - } - - s1, found := oc.Get("*struct") - if !found { - t.Error("*struct was not found") - } - if s1.(*TestStruct).Num != 1 { - t.Error("*struct.Num is not 1") - } - - s2, found := oc.Get("[]struct") - if !found { - t.Error("[]struct was not found") - } - s2r := s2.([]TestStruct) - if len(s2r) != 2 { - t.Error("Length of s2r is not 2") - } - if s2r[0].Num != 2 { - t.Error("s2r[0].Num is not 2") - } - if s2r[1].Num != 3 { - t.Error("s2r[1].Num is not 3") - } - - s3, found := oc.get("[]*struct") - if !found { - t.Error("[]*struct was not found") - } - s3r := s3.([]*TestStruct) - if len(s3r) != 2 { - t.Error("Length of s3r is not 2") - } - if s3r[0].Num != 4 { - t.Error("s3r[0].Num is not 4") - } - if s3r[1].Num != 5 { - t.Error("s3r[1].Num is not 5") - } - - s4, found := oc.get("structception") - if !found { - t.Error("structception was not found") - } - s4r := s4.(*TestStruct) - if len(s4r.Children) != 2 { - t.Error("Length of s4r.Children is not 2") - } - if s4r.Children[0].Num != 6174 { - t.Error("s4r.Children[0].Num is not 6174") - } - if s4r.Children[1].Num != 4716 { - t.Error("s4r.Children[1].Num is not 4716") - } -} - -func TestFileSerialization(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Add("a", "a", DefaultExpiration) - tc.Add("b", "b", DefaultExpiration) - f, err := ioutil.TempFile("", "go-cache-cache.dat") - if err != nil { - t.Fatal("Couldn't create cache file:", err) - } - fname := f.Name() - f.Close() - tc.SaveFile(fname) - - oc := New(DefaultExpiration, 0) - oc.Add("a", "aa", 0) // this should not be overwritten - err = oc.LoadFile(fname) - if err != nil { - t.Error(err) - } - a, found := oc.Get("a") - if !found { - t.Error("a was not found") - } - astr := a.(string) - if astr != "aa" { - if astr == "a" { - t.Error("a was overwritten") - } else { - t.Error("a is not aa") - } - } - b, found := oc.Get("b") - if !found { - t.Error("b was not found") - } - if b.(string) != "b" { - t.Error("b is not b") - } -} - -func TestSerializeUnserializable(t *testing.T) { - tc := New(DefaultExpiration, 0) - ch := make(chan bool, 1) - ch <- true - tc.Set("chan", ch, DefaultExpiration) - fp := &bytes.Buffer{} - err := tc.Save(fp) // this should fail gracefully - if err.Error() != "gob NewTypeObject can't handle type: chan bool" { - t.Error("Error from Save was not gob NewTypeObject can't handle type chan bool:", err) - } -} - func BenchmarkCacheGetExpiring(b *testing.B) { benchmarkCacheGet(b, 5*time.Minute) } @@ -1436,7 +177,7 @@ func BenchmarkCacheGetNotExpiring(b *testing.B) { func benchmarkCacheGet(b *testing.B, exp time.Duration) { b.StopTimer() tc := New(exp, 0) - tc.Set("foo", "bar", DefaultExpiration) + tc.Set("foo", 1, DefaultExpiration) b.StartTimer() for i := 0; i < b.N; i++ { tc.Get("foo") @@ -1459,7 +200,7 @@ func BenchmarkRWMutexMapGet(b *testing.B) { func BenchmarkRWMutexInterfaceMapGetStruct(b *testing.B) { b.StopTimer() - s := struct{name string}{name: "foo"} + s := struct{ name string }{name: "foo"} m := map[interface{}]string{ s: "bar", } @@ -1497,7 +238,7 @@ func BenchmarkCacheGetConcurrentNotExpiring(b *testing.B) { func benchmarkCacheGetConcurrent(b *testing.B, exp time.Duration) { b.StopTimer() tc := New(exp, 0) - tc.Set("foo", "bar", DefaultExpiration) + tc.Set("foo", 1, DefaultExpiration) wg := new(sync.WaitGroup) workers := runtime.NumCPU() each := b.N / workers @@ -1555,9 +296,9 @@ func benchmarkCacheGetManyConcurrent(b *testing.B, exp time.Duration) { tc := New(exp, 0) keys := make([]string, n) for i := 0; i < n; i++ { - k := "foo" + strconv.Itoa(n) + k := "foo" + strconv.Itoa(i) keys[i] = k - tc.Set(k, "bar", DefaultExpiration) + tc.Set(k, ValueType(1), DefaultExpiration) } each := b.N / n wg := new(sync.WaitGroup) @@ -1587,7 +328,7 @@ func benchmarkCacheSet(b *testing.B, exp time.Duration) { tc := New(exp, 0) b.StartTimer() for i := 0; i < b.N; i++ { - tc.Set("foo", "bar", DefaultExpiration) + tc.Set("foo", 1, DefaultExpiration) } } @@ -1608,7 +349,7 @@ func BenchmarkCacheSetDelete(b *testing.B) { tc := New(DefaultExpiration, 0) b.StartTimer() for i := 0; i < b.N; i++ { - tc.Set("foo", "bar", DefaultExpiration) + tc.Set("foo", 1, DefaultExpiration) tc.Delete("foo") } } @@ -1634,7 +375,7 @@ func BenchmarkCacheSetDeleteSingleLock(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { tc.mu.Lock() - tc.set("foo", "bar", DefaultExpiration) + tc.set("foo", 1, DefaultExpiration) tc.delete("foo") tc.mu.Unlock() } @@ -1654,12 +395,13 @@ func BenchmarkRWMutexMapSetDeleteSingleLock(b *testing.B) { } func BenchmarkIncrementInt(b *testing.B) { + b.Skip() b.StopTimer() tc := New(DefaultExpiration, 0) tc.Set("foo", 0, DefaultExpiration) b.StartTimer() for i := 0; i < b.N; i++ { - tc.IncrementInt("foo", 1) + // tc.IncrementInt("foo", 1) } } @@ -1668,7 +410,7 @@ func BenchmarkDeleteExpiredLoop(b *testing.B) { tc := New(5*time.Minute, 0) tc.mu.Lock() for i := 0; i < 100000; i++ { - tc.set(strconv.Itoa(i), "bar", DefaultExpiration) + tc.set(strconv.Itoa(i), 1, DefaultExpiration) } tc.mu.Unlock() b.StartTimer() diff --git a/sharded.go b/sharded.go index bcc0538..b1ab771 100644 --- a/sharded.go +++ b/sharded.go @@ -66,19 +66,19 @@ func (sc *shardedCache) bucket(k string) *cache { return sc.cs[djb33(sc.seed, k)%sc.m] } -func (sc *shardedCache) Set(k string, x interface{}, d time.Duration) { +func (sc *shardedCache) Set(k string, x ValueType, d time.Duration) { sc.bucket(k).Set(k, x, d) } -func (sc *shardedCache) Add(k string, x interface{}, d time.Duration) error { +func (sc *shardedCache) Add(k string, x ValueType, d time.Duration) error { return sc.bucket(k).Add(k, x, d) } -func (sc *shardedCache) Replace(k string, x interface{}, d time.Duration) error { +func (sc *shardedCache) Replace(k string, x ValueType, d time.Duration) error { return sc.bucket(k).Replace(k, x, d) } -func (sc *shardedCache) Get(k string) (interface{}, bool) { +func (sc *shardedCache) Get(k string) *ValueType { return sc.bucket(k).Get(k) } @@ -86,10 +86,6 @@ func (sc *shardedCache) Increment(k string, n int64) error { return sc.bucket(k).Increment(k, n) } -func (sc *shardedCache) IncrementFloat(k string, n float64) error { - return sc.bucket(k).IncrementFloat(k, n) -} - func (sc *shardedCache) Decrement(k string, n int64) error { return sc.bucket(k).Decrement(k, n) } @@ -109,12 +105,9 @@ func (sc *shardedCache) DeleteExpired() { // fields of the items should be checked. Note that explicit synchronization // is needed to use a cache and its corresponding Items() return values at // the same time, as the maps are shared. -func (sc *shardedCache) Items() []map[string]Item { - res := make([]map[string]Item, len(sc.cs)) - for i, v := range sc.cs { - res[i] = v.Items() - } - return res +// TODO: 不准备暴露这个接口,使用者不应该知道底层的数据. +func (sc *shardedCache) items() []map[string]Item { + return nil } func (sc *shardedCache) Flush() { diff --git a/sharded_test.go b/sharded_test.go index aef8597..01e1e11 100644 --- a/sharded_test.go +++ b/sharded_test.go @@ -29,7 +29,7 @@ var shardedKeys = []string{ func TestShardedCache(t *testing.T) { tc := unexportedNewSharded(DefaultExpiration, 0, 13) for _, v := range shardedKeys { - tc.Set(v, "value", DefaultExpiration) + tc.Set(v, ValueType(1), DefaultExpiration) } } @@ -44,7 +44,7 @@ func BenchmarkShardedCacheGetNotExpiring(b *testing.B) { func benchmarkShardedCacheGet(b *testing.B, exp time.Duration) { b.StopTimer() tc := unexportedNewSharded(exp, 0, 10) - tc.Set("foobarba", "zquux", DefaultExpiration) + tc.Set("foobarba", ValueType(1), DefaultExpiration) b.StartTimer() for i := 0; i < b.N; i++ { tc.Get("foobarba") @@ -65,9 +65,9 @@ func benchmarkShardedCacheGetManyConcurrent(b *testing.B, exp time.Duration) { tsc := unexportedNewSharded(exp, 0, 20) keys := make([]string, n) for i := 0; i < n; i++ { - k := "foo" + strconv.Itoa(n) + k := "foo" + strconv.Itoa(i) keys[i] = k - tsc.Set(k, "bar", DefaultExpiration) + tsc.Set(k, ValueType(1), DefaultExpiration) } each := b.N / n wg := new(sync.WaitGroup) diff --git a/valtyp.go b/valtyp.go new file mode 100644 index 0000000..b31a4d3 --- /dev/null +++ b/valtyp.go @@ -0,0 +1,3 @@ +package cache + +type ValueType int From 96eafc2dcdc5386986b5de50fde4ec02a388ae38 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Thu, 11 Aug 2016 16:42:28 +0800 Subject: [PATCH 02/14] Add generator for code Signed-off-by: Peng Gao --- CONTRIBUTORS | 3 +- cache.go | 2 +- cachemap/main.go | 361 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 cachemap/main.go diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 8a4da4e..123cbd7 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,4 +1,4 @@ -This is a list of people who have contributed code to go-cache. They, or their +This is a list of people who have contributed code to cachemap. They, or their employers, are the copyright holders of the contributed code. Contributed code is subject to the license restrictions listed in LICENSE (as they were when the code was contributed.) @@ -6,3 +6,4 @@ code was contributed.) Dustin Sallings Jason Mooberry Sergey Shepelev +Peng Gao diff --git a/cache.go b/cache.go index 213e180..cc180ae 100644 --- a/cache.go +++ b/cache.go @@ -21,7 +21,7 @@ func (item Item) Expired() bool { } const ( - // For use with functions that take an expiration time. + // For use with functions that take no expiration time. NoExpiration time.Duration = -1 // For use with functions that take an expiration time. Equivalent to // passing in the same expiration duration as was given to New() or diff --git a/cachemap/main.go b/cachemap/main.go new file mode 100644 index 0000000..362cb1c --- /dev/null +++ b/cachemap/main.go @@ -0,0 +1,361 @@ +package main + +import ( + "flag" + "fmt" + "go/parser" + "go/token" + "os" + "text/template" +) + +var cachemapTemplate = `// Automatically generated file; DO NOT EDIT +package {{ .PackageName }} + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +type Item struct { + Object {{ .ValueType }} + Expiration int64 +} + +// Returns true if the item has expired. +func (item Item) Expired() bool { + if item.Expiration == 0 { + return false + } + return time.Now().UnixNano() > item.Expiration +} + +const ( + // For use with functions that take no expiration time. + NoExpiration time.Duration = -1 + // For use with functions that take an expiration time. Equivalent to + // passing in the same expiration duration as was given to {{ .Cache }}New(). + DefaultExpiration time.Duration = 0 +) + +type {{ .Cache }} struct { + *cache + // If this is confusing, see the comment at the bottom of {{ .Cache }}New() +} + +type cache struct { + defaultExpiration time.Duration + items map[string]Item + mu sync.RWMutex + onEvicted func(string, *{{ .ValueType }}) + janitor *janitor +} + +// 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) { + // "Inlining" of set + var e int64 + if d == DefaultExpiration { + d = c.defaultExpiration + } + if d > 0 { + e = time.Now().Add(d).UnixNano() + } + c.mu.Lock() + c.items[k] = Item{ + Object: x, + Expiration: e, + } + // TODO: Calls to mu.Unlock are currently not deferred because defer + // adds ~200 ns (as of go1.) + c.mu.Unlock() +} + +func (c *cache) set(k string, x {{ .ValueType }}, d time.Duration) { + var e int64 + if d == DefaultExpiration { + d = c.defaultExpiration + } + if d > 0 { + e = time.Now().Add(d).UnixNano() + } + c.items[k] = Item{ + Object: x, + Expiration: e, + } +} + +// 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 { + c.mu.Lock() + _, found := c.get(k) + if found { + c.mu.Unlock() + return fmt.Errorf("Item %s already exists", k) + } + c.set(k, x, d) + c.mu.Unlock() + return nil +} + +// 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 { + c.mu.Lock() + _, found := c.get(k) + if !found { + c.mu.Unlock() + return fmt.Errorf("Item %s doesn't exist", k) + } + c.set(k, x, d) + c.mu.Unlock() + return nil +} + +// 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 }} { + c.mu.RLock() + // "Inlining" of get and Expired + item, found := c.items[k] + if !found { + c.mu.RUnlock() + return nil + } + if item.Expiration > 0 { + if time.Now().UnixNano() > item.Expiration { + c.mu.RUnlock() + return nil + } + } + c.mu.RUnlock() + return &item.Object +} + +func (c *cache) get(k string) (*{{ .ValueType }}, bool) { + item, found := c.items[k] + if !found { + return nil, false + } + // "Inlining" of Expired + if item.Expiration > 0 { + if time.Now().UnixNano() > item.Expiration { + return nil, false + } + } + return &item.Object, true +} + +// Increment an item of type int, int8, int16, int32, int64, uintptr, uint, +// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the +// 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. +// TODO: Increment for numberic type. +func (c *cache) Increment(k string, n int64) error { + return nil +} + +// Decrement an item of type int, int8, int16, int32, int64, uintptr, uint, +// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the +// 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. +// TODO: Decrement +func (c *cache) Decrement(k string, n int64) error { + // TODO: Implement Increment and Decrement more cleanly. + // (Cannot do Increment(k, n*-1) for uints.) + return nil +} + +// Delete an item from the cache. Does nothing if the key is not in the cache. +func (c *cache) Delete(k string) { + c.mu.Lock() + v, evicted := c.delete(k) + c.mu.Unlock() + if evicted { + c.onEvicted(k, v) + } +} + +func (c *cache) delete(k string) (*{{ .ValueType }}, bool) { + if c.onEvicted != nil { + if v, found := c.items[k]; found { + delete(c.items, k) + return &v.Object, true + } + } + delete(c.items, k) + return nil, false +} + +type keyAndValue struct { + key string + value *{{ .ValueType }} +} + +// Delete all expired items from the cache. +func (c *cache) DeleteExpired() { + var evictedItems []keyAndValue + now := time.Now().UnixNano() + c.mu.Lock() + for k, v := range c.items { + // "Inlining" of expired + if v.Expiration > 0 && now > v.Expiration { + ov, evicted := c.delete(k) + if evicted { + evictedItems = append(evictedItems, keyAndValue{k, ov}) + } + } + } + c.mu.Unlock() + for _, v := range evictedItems { + c.onEvicted(v.key, v.value) + } +} + +// Sets an (optional) function that is called with the key and value when an +// item is evicted from the cache. (Including when it is deleted manually, but +// not when it is overwritten.) Set to nil to disable. +func (c *cache) OnEvicted(f func(string, *{{ .ValueType }})) { + c.mu.Lock() + c.onEvicted = f + c.mu.Unlock() +} + +// 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 { + c.mu.RLock() + n := len(c.items) + c.mu.RUnlock() + return n +} + +// Delete all items from the cache. +func (c *cache) Flush() { + c.mu.Lock() + c.items = map[string]Item{} + c.mu.Unlock() +} + +type janitor struct { + Interval time.Duration + stop chan bool +} + +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 stopJanitor(c *{{ .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) *{{ .Cache }} { + c := newCache(de, m) + // 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 }}{c} + if ci > 0 { + runJanitor(c, ci) + // 如果C被回收了,但是c不会,因为stopJanitor是一个一直运行的 + // goroutine对c一直有引用不会被回收,所以加一个Finalizer来停掉 + // 这个goroutine然后让c被回收. + runtime.SetFinalizer(C, stopJanitor) + } + return C +} + +// Return a new cache with a given default expiration duration and cleanup +// interval. If the expiration duration is less than one (or NoExpiration), +// the items in the cache never expire (by default), and must be deleted +// manually. If the cleanup interval is less than one, expired items are not +// deleted from the cache before calling c.DeleteExpired(). +func {{ .Cache }}New(defaultExpiration, cleanupInterval time.Duration) *{{ .Cache }}{ + items := make(map[string]Item) + return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) +}` + +func fatal(v ...interface{}) { + fmt.Fprintln(os.Stderr, v...) + os.Exit(1) +} + +func main() { + keyType := flag.String("k", "", "key type") + valueType := flag.String("v", "", "value type") + flag.Parse() + if *keyType == "" { + fatal("key empty") + } + if *valueType == "" { + fatal("value empty") + } + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, ".", nil, parser.ParseComments) + if err != nil { + fatal(err) + } + var packageName string + for name := range pkgs { + packageName = name + } + f, err := os.OpenFile(fmt.Sprintf("%s2%s_cachemap.go", *keyType, *valueType), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + fatal(err) + } + defer f.Close() + tpl, err := template.New("cachemap").Parse(cachemapTemplate) + if err != nil { + fatal(err) + } + err = tpl.Execute( + f, + map[string]string{ + "ValueType": *valueType, + "PackageName": packageName, + "Cache": fmt.Sprintf("String2%sCache", *valueType), + }, + ) + if err != nil { + fatal(err) + } +} From 96d0b686a8434b4101ae73a74e9e11f59fcc7648 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Fri, 26 Aug 2016 14:05:15 +0800 Subject: [PATCH 03/14] Update LICENSE and CONTRIBUTORS Signed-off-by: Peng Gao --- CONTRIBUTORS | 1 + LICENSE | 1 + 2 files changed, 2 insertions(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 123cbd7..6ccce2c 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3,6 +3,7 @@ employers, are the copyright holders of the contributed code. Contributed code is subject to the license restrictions listed in LICENSE (as they were when the code was contributed.) +Peng Gao Dustin Sallings Jason Mooberry Sergey Shepelev diff --git a/LICENSE b/LICENSE index 159e1e7..75c2526 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ Copyright (c) 2012-2015 Patrick Mylund Nielsen and the go-cache contributors +Copyright (c) 2016 Peng Gao and cachemap contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 987311e3b42050193306fa7d48c618bc461ccea8 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Sat, 27 Aug 2016 18:05:46 +0800 Subject: [PATCH 04/14] Refactor code 1. Add benchmark performance in README.md 2. Use an Attr as argument for New to initiate a cache, in this way, more default settings can be supported. 3. Add fast way for delete method, if no onEvicted callback function set. 4. Use bool to indicate k existence in Get method to avoid pointer escape (which leads to memory allocation on heap), reference link below to get detail information. https://groups.google.com/forum/#!topic/golang-nuts/1WCWAtOsyDU Signed-off-by: Peng Gao --- README.md | 28 ++++ cache.go | 146 +++++++++++--------- cache_test.go | 190 ++++++++++++++++++-------- cachemap/cache.tmpl | 324 ++++++++++++++++++++++++++++++++++++++++++++ cachemap/main.go | 318 ++----------------------------------------- sharded.go | 8 +- sharded_test.go | 9 +- valtyp.go | 2 +- 8 files changed, 591 insertions(+), 434 deletions(-) create mode 100644 cachemap/cache.tmpl diff --git a/README.md b/README.md index 168ff7b..bd47807 100644 --- a/README.md +++ b/README.md @@ -105,3 +105,31 @@ one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats ### Reference `godoc` or [http://godoc.org/github.com/patrickmn/go-cache](http://godoc.org/github.com/patrickmn/go-cache) + +### Benchmark + +| benchmark\package | go-cache | cachemap | +|-----------------------------------------------------|-----------------------|----------------------| +| BenchmarkCacheGetExpiring-v | 30000000,46.3 ns/op | 20000000,43.4 ns/op | +| BenchmarkCacheGetNotExpiring-v | 50000000,29.6 ns/op | 50000000,29.6 ns/op | +| BenchmarkRWMutexMapGet-x | 50000000,26.7 ns/op | 50000000,26.6 ns/op | +| BenchmarkRWMutexInterfaceMapGetStruct-x | 20000000,75.1 ns/op | 20000000,66.1 ns/op | +| BenchmarkRWMutexInterfaceMapGetString-x | 20000000,75.3 ns/op | 20000000,67.6 ns/op | +| BenchmarkCacheGetConcurrentExpiring-v | 20000000,67.8 ns/op | 20000000,68.9 ns/op | +| BenchmarkCacheGetConcurrentNotExpiring-v | 20000000,69.2 ns/op | 20000000,68.6 ns/op | +| BenchmarkRWMutexMapGetConcurrent-x | 30000000,57.4 ns/op | 20000000,64.7 ns/op | +| BenchmarkCacheGetManyConcurrentExpiring-v | 100000000,68.0 ns/op | 100000000,66.7 ns/op | +| BenchmarkCacheGetManyConcurrentNotExpiring-v | 2000000000,68.3 ns/op | 20000000,69.3 ns/op | +| BenchmarkCacheSetExpiring-4 | 10000000,173 ns/op | 20000000,91.4 ns/op | +| BenchmarkCacheSetNotExpiring-4 | 10000000,123 ns/op | 20000000,100 ns/op | +| BenchmarkRWMutexMapSet-4 | 20000000,88.5 ns/op | 20000000,74.5 ns/op | +| BenchmarkCacheSetDelete-4 | 5000000,257 ns/op | 10000000,151 ns/op | +| BenchmarkRWMutexMapSetDelete-4 | 10000000,180 ns/op | 10000000,154 ns/op | +| BenchmarkCacheSetDeleteSingleLock-4 | 10000000,211 ns/op | 20000000,118 ns/op | +| BenchmarkRWMutexMapSetDeleteSingleLock-4 | 10000000,142 ns/op | 20000000,118 ns/op | +| BenchmarkIncrementInt-4 | 10000000,167 ns/op | | +| BenchmarkDeleteExpiredLoop-4 | 500,2584384 ns/op | 1000,2173019 ns/op | +| BenchmarkShardedCacheGetExpiring-4 | 20000000,79.5 ns/op | 20000000,67.9 ns/op | +| BenchmarkShardedCacheGetNotExpiring-4 | 30000000,59.3 ns/op | 20000000,49.9 ns/op | +| BenchmarkShardedCacheGetManyConcurrentExpiring-4 | 2000000000,52.4 ns/op | 10000000,75.8 ns/op | +| BenchmarkShardedCacheGetManyConcurrentNotExpiring-4 | 100000000,68.2 ns/op | 20000000,75.8 ns/op | diff --git a/cache.go b/cache.go index cc180ae..134f9c7 100644 --- a/cache.go +++ b/cache.go @@ -1,5 +1,7 @@ package cache +// The package is used as a template, don't use it directly! + import ( "fmt" "runtime" @@ -7,29 +9,41 @@ import ( "time" ) +// Attr is cachmap attribute +type Attr_tpl struct { + // An (optional) function that is called with the key and value when an + // item is evicted from the cache. (Including when it is deleted manually, but + // not when it is overwritten.) Set to nil to disable. + OnEvicted func(k string, v ValueType_tpl) + + DefaultCleanupInterval time.Duration // default clean interval + DefaultExpiration time.Duration // default expiration duration + Size int64 // initial size of map +} + +// Item struct type Item struct { - Object ValueType + Object ValueType_tpl Expiration int64 } -// Returns true if the item has expired. +// Expired Returns true if the item has expired, if valid Expiration is set. func (item Item) Expired() bool { - if item.Expiration == 0 { - return false - } - return time.Now().UnixNano() > item.Expiration + return item.Expiration != 0 && time.Now().UnixNano() > item.Expiration } const ( - // For use with functions that take no expiration time. + // NoExpiration is for use with functions that take no expiration time. NoExpiration time.Duration = -1 - // For use with functions that take an expiration time. Equivalent to - // passing in the same expiration duration as was given to New() or - // NewFrom() when the cache was created (e.g. 5 minutes.) + // DefaultExpiration is for use with functions that take an + // expiration time. Equivalent to passing in the same expiration + // duration as was given to New() or NewFrom() when the cache was + // created (e.g. 5 minutes.) DefaultExpiration time.Duration = 0 ) -type Cache struct { +// Cache struct +type Cache_tpl struct { *cache // If this is confusing, see the comment at the bottom of New() } @@ -38,14 +52,14 @@ type cache struct { defaultExpiration time.Duration items map[string]Item mu sync.RWMutex - onEvicted func(string, *ValueType) + onEvicted func(string, ValueType_tpl) janitor *janitor } // 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 *cache) Set(k string, x ValueType_tpl, d time.Duration) { // "Inlining" of set var e int64 if d == DefaultExpiration { @@ -64,7 +78,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 *cache) set(k string, x ValueType_tpl, d time.Duration) { var e int64 if d == DefaultExpiration { d = c.defaultExpiration @@ -80,7 +94,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 *cache) Add(k string, x ValueType_tpl, d time.Duration) error { c.mu.Lock() _, found := c.get(k) if found { @@ -94,7 +108,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 *cache) Replace(k string, x ValueType_tpl, d time.Duration) error { c.mu.Lock() _, found := c.get(k) if !found { @@ -108,35 +122,24 @@ 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 { +func (c *cache) Get(k string) (ValueType_tpl, bool) { c.mu.RLock() // "Inlining" of get and Expired item, found := c.items[k] - if !found { + // TODO: inline time.Now implementation + if !found || item.Expiration > 0 && time.Now().UnixNano() > item.Expiration { c.mu.RUnlock() - return nil - } - if item.Expiration > 0 { - if time.Now().UnixNano() > item.Expiration { - c.mu.RUnlock() - return nil - } + return ValueType_tpl(0), false } c.mu.RUnlock() - return &item.Object + return item.Object, true } -func (c *cache) get(k string) (*ValueType, bool) { +func (c *cache) get(k string) (*ValueType_tpl, bool) { item, found := c.items[k] - if !found { + if !found || item.Expiration > 0 && time.Now().UnixNano() > item.Expiration { return nil, false } - // "Inlining" of Expired - if item.Expiration > 0 { - if time.Now().UnixNano() > item.Expiration { - return nil, false - } - } return &item.Object, true } @@ -164,6 +167,14 @@ func (c *cache) Decrement(k string, n int64) error { // Delete an item from the cache. Does nothing if the key is not in the cache. func (c *cache) Delete(k string) { + // fast path + if c.onEvicted == nil { + c.mu.Lock() + c.deleteFast(k) + c.mu.Unlock() + return + } + // slow path c.mu.Lock() v, evicted := c.delete(k) c.mu.Unlock() @@ -172,26 +183,41 @@ func (c *cache) Delete(k string) { } } -func (c *cache) delete(k string) (*ValueType, bool) { - if c.onEvicted != nil { - if v, found := c.items[k]; found { - delete(c.items, k) - return &v.Object, true - } +func (c *cache) delete(k string) (ValueType_tpl, bool) { + if v, found := c.items[k]; found { + delete(c.items, k) + return v.Object, true } + //TODO: zeroValue + return 0, false +} + +func (c *cache) deleteFast(k string) { delete(c.items, k) - return nil, false } type keyAndValue struct { key string - value *ValueType + value ValueType_tpl } // Delete all expired items from the cache. func (c *cache) DeleteExpired() { var evictedItems []keyAndValue now := time.Now().UnixNano() + // fast path + if c.onEvicted == nil { + c.mu.Lock() + for k, v := range c.items { + // "Inlining" of expired + if v.Expiration > 0 && now > v.Expiration { + c.deleteFast(k) + } + } + c.mu.Unlock() + return + } + // slow path c.mu.Lock() for k, v := range c.items { // "Inlining" of expired @@ -208,15 +234,6 @@ func (c *cache) DeleteExpired() { } } -// Sets an (optional) function that is called with the key and value when an -// item is evicted from the cache. (Including when it is deleted manually, but -// not when it is overwritten.) Set to nil to disable. -func (c *cache) OnEvicted(f func(string, *ValueType)) { - c.mu.Lock() - c.onEvicted = f - c.mu.Unlock() -} - // 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 { @@ -252,7 +269,7 @@ func (j *janitor) Run(c *cache) { } } -func stopJanitor(c *Cache) { +func stopJanitor(c *Cache_tpl) { c.janitor.stop <- true } @@ -275,30 +292,29 @@ func newCache(de time.Duration, m map[string]Item) *cache { return c } -func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache { +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{c} + C := &Cache_tpl{c} if ci > 0 { runJanitor(c, ci) - // 如果C被回收了,但是c不会,因为stopJanitor是一个一直运行的 - // goroutine对c一直有引用不会被回收,所以加一个Finalizer来停掉 - // 这个goroutine然后让c被回收. runtime.SetFinalizer(C, stopJanitor) } return C } -// Return a new cache with a given default expiration duration and cleanup -// interval. If the expiration duration is less than one (or NoExpiration), -// the items in the cache never expire (by default), and must be deleted -// manually. If the cleanup interval is less than one, expired items are not -// deleted from the cache before calling c.DeleteExpired(). -func New(defaultExpiration, cleanupInterval time.Duration) *Cache { - items := make(map[string]Item) - return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) +// New Returns a new cache with a given default expiration duration and +// cleanup interval. If the expiration duration is less than one +// (or NoExpiration), the items in the cache never expire (by default), +// and must be deleted manually. If the cleanup interval is less than one, +// 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) } diff --git a/cache_test.go b/cache_test.go index b0e077b..c1a4d80 100644 --- a/cache_test.go +++ b/cache_test.go @@ -14,67 +14,98 @@ type TestStruct struct { } func TestCache(t *testing.T) { - tc := New(DefaultExpiration, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) - a := tc.Get("a") - if a != nil { + a, found := tc.Get("a") + if found || a != 0 { t.Error("Getting A found value that shouldn't exist:", a) } - b := tc.Get("b") - if b != nil { + b, found := tc.Get("b") + if found || b != 0 { t.Error("Getting B found value that shouldn't exist:", b) } - c := tc.Get("c") - if c != nil { + c, found := tc.Get("c") + if found || c != 0 { t.Error("Getting C found value that shouldn't exist:", c) } - tc.Set("a", ValueType(1), DefaultExpiration) + tc.Set("a", 1, DefaultExpiration) + tc.Set("b", 2, DefaultExpiration) + tc.Set("c", 3, DefaultExpiration) - x := tc.Get("a") - if x == nil { + x, found := tc.Get("a") + if !found { + t.Error("a was not found while getting a2") + } + if x == 0 { t.Error("x for a is nil") - } else if a2 := int(*x); a2+2 != 3 { + } else if a2 := x; a2+2 != 3 { t.Error("a2 (which should be 1) plus 2 does not equal 3; value:", a2) } + x, found = tc.Get("b") + if !found { + t.Error("b was not found while getting b2") + } + if x == 0 { + t.Error("x for b is nil") + } else if b2 := x; b2+2 != 4 { + t.Error("b2 (which should be 2) plus 2 does not equal 4; value:", b2) + } + + x, found = tc.Get("c") + if !found { + t.Error("c was not found while getting c2") + } + if x == 0 { + t.Error("x for c is nil") + } else if c2 := x; c2+1 != 4 { + t.Error("c2 (which should be 3) plus 1 does not equal 4; value:", c2) + } + } func TestCacheTimes(t *testing.T) { - var x *ValueType + var found bool - tc := New(50*time.Millisecond, 1*time.Millisecond) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: 50 * time.Millisecond, + DefaultCleanupInterval: 1 * time.Millisecond, + }) tc.Set("a", 1, DefaultExpiration) tc.Set("b", 2, NoExpiration) tc.Set("c", 3, 20*time.Millisecond) tc.Set("d", 4, 70*time.Millisecond) <-time.After(25 * time.Millisecond) - x = tc.Get("c") - if x != nil { - t.Error("Found c when it should have been automatically deleted", *x) + _, found = tc.Get("c") + if found { + t.Error("Found c when it should have been automatically deleted") } <-time.After(30 * time.Millisecond) - x = tc.Get("a") - if x != nil { - t.Error("Found a when it should have been automatically deleted", *x) + _, found = tc.Get("a") + if found { + t.Error("Found a when it should have been automatically deleted") } - x = tc.Get("b") - if x == nil { + _, found = tc.Get("b") + if !found { t.Error("Did not find b even though it was set to never expire") } - x = tc.Get("d") - if x == nil { + _, found = tc.Get("d") + if !found { t.Error("Did not find d even though it was set to expire later than the default") } <-time.After(20 * time.Millisecond) - x = tc.Get("d") - if x != nil { + _, found = tc.Get("d") + if found { t.Error("Found d when it should have been automatically deleted (later than the default)") } } @@ -84,7 +115,10 @@ func TestIncrementWithInt(t *testing.T) { } func TestAdd(t *testing.T) { - tc := New(DefaultExpiration, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) err := tc.Add("foo", 1, DefaultExpiration) if err != nil { t.Error("Couldn't add foo even though it shouldn't exist") @@ -96,7 +130,10 @@ func TestAdd(t *testing.T) { } func TestReplace(t *testing.T) { - tc := New(DefaultExpiration, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: -1, + }) err := tc.Replace("foo", 1, DefaultExpiration) if err == nil { t.Error("Replaced foo when it shouldn't exist") @@ -107,19 +144,27 @@ func TestReplace(t *testing.T) { t.Error("Couldn't replace existing key foo") } } - func TestDelete(t *testing.T) { - tc := New(DefaultExpiration, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) tc.Set("foo", 1, DefaultExpiration) tc.Delete("foo") - x := tc.Get("foo") - if x != nil { + x, found := tc.Get("foo") + if found { + t.Error("foo was found, but it should have been deleted") + } + if x != 0 { t.Error("x is not nil:", x) } } func TestItemCount(t *testing.T) { - tc := New(DefaultExpiration, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) tc.Set("foo", 1, DefaultExpiration) tc.Set("bar", 2, DefaultExpiration) tc.Set("baz", 3, DefaultExpiration) @@ -129,39 +174,51 @@ func TestItemCount(t *testing.T) { } func TestFlush(t *testing.T) { - tc := New(DefaultExpiration, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: -1, + }) tc.Set("foo", 1, DefaultExpiration) tc.Set("baz", 2, DefaultExpiration) tc.Flush() - x := tc.Get("foo") - if x != nil { + x, found := tc.Get("foo") + if found { + t.Error("foo was found, but it should have been deleted") + } + if x != 0 { t.Error("x is not nil:", x) } - x = tc.Get("baz") - if x != nil { + x, found = tc.Get("baz") + if found { + t.Error("baz was found, but it should have been deleted") + } + if x != 0 { t.Error("x is not nil:", x) } } func TestOnEvicted(t *testing.T) { - tc := New(DefaultExpiration, 0) - tc.Set("foo", ValueType(3), DefaultExpiration) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) + tc.Set("foo", 3, DefaultExpiration) if tc.onEvicted != nil { t.Fatal("tc.onEvicted is not nil") } works := false - tc.OnEvicted(func(k string, v *ValueType) { - if k == "foo" && int(*v) == 3 { + tc.OnEvicted(func(k string, v ValueType_tpl) { + if k == "foo" && v == 3 { works = true } - tc.Set("bar", ValueType(4), DefaultExpiration) + tc.Set("bar", 4, DefaultExpiration) }) tc.Delete("foo") - x := tc.Get("bar") + x, _ := tc.Get("bar") if !works { t.Error("works bool not true") } - if int(*x) != 4 { + if x != 4 { t.Error("bar was not 4") } } @@ -176,7 +233,10 @@ func BenchmarkCacheGetNotExpiring(b *testing.B) { func benchmarkCacheGet(b *testing.B, exp time.Duration) { b.StopTimer() - tc := New(exp, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: exp, + DefaultCleanupInterval: 0, + }) tc.Set("foo", 1, DefaultExpiration) b.StartTimer() for i := 0; i < b.N; i++ { @@ -237,7 +297,10 @@ func BenchmarkCacheGetConcurrentNotExpiring(b *testing.B) { func benchmarkCacheGetConcurrent(b *testing.B, exp time.Duration) { b.StopTimer() - tc := New(exp, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: exp, + DefaultCleanupInterval: 0, + }) tc.Set("foo", 1, DefaultExpiration) wg := new(sync.WaitGroup) workers := runtime.NumCPU() @@ -293,20 +356,24 @@ func benchmarkCacheGetManyConcurrent(b *testing.B, exp time.Duration) { // in sharded_test.go. b.StopTimer() n := 10000 - tc := New(exp, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: exp, + DefaultCleanupInterval: 0, + }) keys := make([]string, n) for i := 0; i < n; i++ { k := "foo" + strconv.Itoa(i) keys[i] = k - tc.Set(k, ValueType(1), DefaultExpiration) + tc.Set(k, ValueType_tpl(1), DefaultExpiration) } each := b.N / n wg := new(sync.WaitGroup) wg.Add(n) for _, v := range keys { + x := v go func() { for j := 0; j < each; j++ { - tc.Get(v) + tc.Get(x) } wg.Done() }() @@ -325,7 +392,10 @@ func BenchmarkCacheSetNotExpiring(b *testing.B) { func benchmarkCacheSet(b *testing.B, exp time.Duration) { b.StopTimer() - tc := New(exp, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: exp, + DefaultCleanupInterval: 0, + }) b.StartTimer() for i := 0; i < b.N; i++ { tc.Set("foo", 1, DefaultExpiration) @@ -346,7 +416,10 @@ func BenchmarkRWMutexMapSet(b *testing.B) { func BenchmarkCacheSetDelete(b *testing.B) { b.StopTimer() - tc := New(DefaultExpiration, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) b.StartTimer() for i := 0; i < b.N; i++ { tc.Set("foo", 1, DefaultExpiration) @@ -371,7 +444,10 @@ func BenchmarkRWMutexMapSetDelete(b *testing.B) { func BenchmarkCacheSetDeleteSingleLock(b *testing.B) { b.StopTimer() - tc := New(DefaultExpiration, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) b.StartTimer() for i := 0; i < b.N; i++ { tc.mu.Lock() @@ -397,7 +473,10 @@ func BenchmarkRWMutexMapSetDeleteSingleLock(b *testing.B) { func BenchmarkIncrementInt(b *testing.B) { b.Skip() b.StopTimer() - tc := New(DefaultExpiration, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) tc.Set("foo", 0, DefaultExpiration) b.StartTimer() for i := 0; i < b.N; i++ { @@ -407,7 +486,10 @@ func BenchmarkIncrementInt(b *testing.B) { func BenchmarkDeleteExpiredLoop(b *testing.B) { b.StopTimer() - tc := New(5*time.Minute, 0) + tc := New_tpl(Attr_tpl{ + DefaultExpiration: 5 * time.Minute, + DefaultCleanupInterval: 0, + }) tc.mu.Lock() for i := 0; i < 100000; i++ { tc.set(strconv.Itoa(i), 1, DefaultExpiration) diff --git a/cachemap/cache.tmpl b/cachemap/cache.tmpl new file mode 100644 index 0000000..26dc373 --- /dev/null +++ b/cachemap/cache.tmpl @@ -0,0 +1,324 @@ +package {{.PackageName}} + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +// Attr is cachmap attribute +type {{.ValueType}}CacheAttr struct { + OnEvicted func(k string, v {{.ValueType}}) // called when k evicted if set + DefaultCleanupInterval time.Duration // default clean interval + DefaultExpiration time.Duration // default expiration duration + Size int64 // inital size of map +} + +// Item struct +type Item struct { + Object {{.ValueType}} + Expiration int64 +} + +// Expired Returns true if the item has expired, if valid Expiration is set. +func (item Item) Expired() bool { + return item.Expiration != 0 && time.Now().UnixNano() > item.Expiration +} + +const ( + // NoExpiration is for use with functions that take no expiration time. + NoExpiration time.Duration = -1 + // DefaultExpiration is for use with functions that take an + // expiration time. Equivalent to passing in the same expiration + // duration as was given to New() or NewFrom() when the cache was + // created (e.g. 5 minutes.) + DefaultExpiration time.Duration = 0 +) + +// Cache struct +type {{.ValueType}}Cache struct { + *cache + // If this is confusing, see the comment at the bottom of New() +} + +type cache struct { + defaultExpiration time.Duration + items map[string]Item + mu sync.RWMutex + onEvicted func(string, {{.ValueType}}) + janitor *janitor +} + +// 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) { + // "Inlining" of set + var e int64 + if d == DefaultExpiration { + d = c.defaultExpiration + } + if d > 0 { + e = time.Now().Add(d).UnixNano() + } + c.mu.Lock() + c.items[k] = Item{ + Object: x, + Expiration: e, + } + // TODO: Calls to mu.Unlock are currently not deferred because defer + // adds ~200 ns (as of go1.) + c.mu.Unlock() +} + +func (c *cache) set(k string, x {{.ValueType}}, d time.Duration) { + var e int64 + if d == DefaultExpiration { + d = c.defaultExpiration + } + if d > 0 { + e = time.Now().Add(d).UnixNano() + } + c.items[k] = Item{ + Object: x, + Expiration: e, + } +} + +// 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 { + c.mu.Lock() + _, found := c.get(k) + if found { + c.mu.Unlock() + return fmt.Errorf("Item %s already exists", k) + } + c.set(k, x, d) + c.mu.Unlock() + return nil +} + +// 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 { + c.mu.Lock() + _, found := c.get(k) + if !found { + c.mu.Unlock() + return fmt.Errorf("Item %s doesn't exist", k) + } + c.set(k, x, d) + c.mu.Unlock() + return nil +} + +// 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) { + c.mu.RLock() + // "Inlining" of get and Expired + item, found := c.items[k] + // TODO: inline time.Now implementation + if !found || item.Expiration > 0 && time.Now().UnixNano() > item.Expiration { + c.mu.RUnlock() + return {{.ValueType}}(0), false + } + c.mu.RUnlock() + return item.Object, true +} + +func (c *cache) get(k string) (*{{.ValueType}}, bool) { + item, found := c.items[k] + if !found || item.Expiration > 0 && time.Now().UnixNano() > item.Expiration { + return nil, false + } + return &item.Object, true +} + +// Increment an item of type int, int8, int16, int32, int64, uintptr, uint, +// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the +// 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. +// TODO: Increment for numberic type. +func (c *cache) Increment(k string, n int64) error { + return nil +} + +// Decrement an item of type int, int8, int16, int32, int64, uintptr, uint, +// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the +// 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. +// TODO: Decrement +func (c *cache) Decrement(k string, n int64) error { + // TODO: Implement Increment and Decrement more cleanly. + // (Cannot do Increment(k, n*-1) for uints.) + return nil +} + +// Delete an item from the cache. Does nothing if the key is not in the cache. +func (c *cache) Delete(k string) { + // fast path + if c.onEvicted == nil { + c.mu.Lock() + c.deleteFast(k) + c.mu.Unlock() + return + } + // slow path + c.mu.Lock() + v, evicted := c.delete(k) + c.mu.Unlock() + if evicted { + c.onEvicted(k, v) + } +} + +func (c *cache) delete(k string) ({{.ValueType}}, bool) { + if v, found := c.items[k]; found { + delete(c.items, k) + return v.Object, true + } + //TODO: zeroValue + return 0, false +} + +func (c *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 + now := time.Now().UnixNano() + // fast path + if c.onEvicted == nil { + c.mu.Lock() + for k, v := range c.items { + // "Inlining" of expired + if v.Expiration > 0 && now > v.Expiration { + c.deleteFast(k) + } + } + c.mu.Unlock() + return + } + // slow path + c.mu.Lock() + for k, v := range c.items { + // "Inlining" of expired + if v.Expiration > 0 && now > v.Expiration { + ov, evicted := c.delete(k) + if evicted { + evictedItems = append(evictedItems, keyAndValue{k, ov}) + } + } + } + c.mu.Unlock() + for _, v := range evictedItems { + c.onEvicted(v.key, v.value) + } +} + +// Sets an (optional) function that is called with the key and value when an +// item is evicted from the cache. (Including when it is deleted manually, but +// not when it is overwritten.) Set to nil to disable. +// 这里加锁没有意义 +func (c *cache) OnEvicted(f func(string, {{.ValueType}})) { + c.mu.Lock() + c.onEvicted = f + c.mu.Unlock() +} + +// 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 { + c.mu.RLock() + n := len(c.items) + c.mu.RUnlock() + return n +} + +// Delete all items from the cache. +func (c *cache) Flush() { + c.mu.Lock() + c.items = map[string]Item{} + c.mu.Unlock() +} + +type janitor struct { + Interval time.Duration + stop chan bool +} + +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 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 +// cleanup interval. If the expiration duration is less than one +// (or NoExpiration), the items in the cache never expire (by default), +// and must be deleted manually. If the cleanup interval is less than one, +// 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) +} diff --git a/cachemap/main.go b/cachemap/main.go index 362cb1c..3c2dd4e 100644 --- a/cachemap/main.go +++ b/cachemap/main.go @@ -6,319 +6,25 @@ import ( "go/parser" "go/token" "os" + "path" + "path/filepath" + "runtime" "text/template" ) -var cachemapTemplate = `// Automatically generated file; DO NOT EDIT -package {{ .PackageName }} - -import ( - "fmt" - "runtime" - "sync" - "time" -) - -type Item struct { - Object {{ .ValueType }} - Expiration int64 -} - -// Returns true if the item has expired. -func (item Item) Expired() bool { - if item.Expiration == 0 { - return false - } - return time.Now().UnixNano() > item.Expiration -} - -const ( - // For use with functions that take no expiration time. - NoExpiration time.Duration = -1 - // For use with functions that take an expiration time. Equivalent to - // passing in the same expiration duration as was given to {{ .Cache }}New(). - DefaultExpiration time.Duration = 0 -) - -type {{ .Cache }} struct { - *cache - // If this is confusing, see the comment at the bottom of {{ .Cache }}New() -} - -type cache struct { - defaultExpiration time.Duration - items map[string]Item - mu sync.RWMutex - onEvicted func(string, *{{ .ValueType }}) - janitor *janitor -} - -// 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) { - // "Inlining" of set - var e int64 - if d == DefaultExpiration { - d = c.defaultExpiration - } - if d > 0 { - e = time.Now().Add(d).UnixNano() - } - c.mu.Lock() - c.items[k] = Item{ - Object: x, - Expiration: e, - } - // TODO: Calls to mu.Unlock are currently not deferred because defer - // adds ~200 ns (as of go1.) - c.mu.Unlock() -} - -func (c *cache) set(k string, x {{ .ValueType }}, d time.Duration) { - var e int64 - if d == DefaultExpiration { - d = c.defaultExpiration - } - if d > 0 { - e = time.Now().Add(d).UnixNano() - } - c.items[k] = Item{ - Object: x, - Expiration: e, - } -} - -// 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 { - c.mu.Lock() - _, found := c.get(k) - if found { - c.mu.Unlock() - return fmt.Errorf("Item %s already exists", k) - } - c.set(k, x, d) - c.mu.Unlock() - return nil -} - -// 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 { - c.mu.Lock() - _, found := c.get(k) - if !found { - c.mu.Unlock() - return fmt.Errorf("Item %s doesn't exist", k) - } - c.set(k, x, d) - c.mu.Unlock() - return nil -} - -// 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 }} { - c.mu.RLock() - // "Inlining" of get and Expired - item, found := c.items[k] - if !found { - c.mu.RUnlock() - return nil - } - if item.Expiration > 0 { - if time.Now().UnixNano() > item.Expiration { - c.mu.RUnlock() - return nil - } - } - c.mu.RUnlock() - return &item.Object -} - -func (c *cache) get(k string) (*{{ .ValueType }}, bool) { - item, found := c.items[k] - if !found { - return nil, false - } - // "Inlining" of Expired - if item.Expiration > 0 { - if time.Now().UnixNano() > item.Expiration { - return nil, false - } - } - return &item.Object, true -} - -// Increment an item of type int, int8, int16, int32, int64, uintptr, uint, -// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the -// 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. -// TODO: Increment for numberic type. -func (c *cache) Increment(k string, n int64) error { - return nil -} - -// Decrement an item of type int, int8, int16, int32, int64, uintptr, uint, -// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the -// 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. -// TODO: Decrement -func (c *cache) Decrement(k string, n int64) error { - // TODO: Implement Increment and Decrement more cleanly. - // (Cannot do Increment(k, n*-1) for uints.) - return nil -} - -// Delete an item from the cache. Does nothing if the key is not in the cache. -func (c *cache) Delete(k string) { - c.mu.Lock() - v, evicted := c.delete(k) - c.mu.Unlock() - if evicted { - c.onEvicted(k, v) - } -} - -func (c *cache) delete(k string) (*{{ .ValueType }}, bool) { - if c.onEvicted != nil { - if v, found := c.items[k]; found { - delete(c.items, k) - return &v.Object, true - } - } - delete(c.items, k) - return nil, false -} - -type keyAndValue struct { - key string - value *{{ .ValueType }} -} - -// Delete all expired items from the cache. -func (c *cache) DeleteExpired() { - var evictedItems []keyAndValue - now := time.Now().UnixNano() - c.mu.Lock() - for k, v := range c.items { - // "Inlining" of expired - if v.Expiration > 0 && now > v.Expiration { - ov, evicted := c.delete(k) - if evicted { - evictedItems = append(evictedItems, keyAndValue{k, ov}) - } - } - } - c.mu.Unlock() - for _, v := range evictedItems { - c.onEvicted(v.key, v.value) - } -} - -// Sets an (optional) function that is called with the key and value when an -// item is evicted from the cache. (Including when it is deleted manually, but -// not when it is overwritten.) Set to nil to disable. -func (c *cache) OnEvicted(f func(string, *{{ .ValueType }})) { - c.mu.Lock() - c.onEvicted = f - c.mu.Unlock() -} - -// 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 { - c.mu.RLock() - n := len(c.items) - c.mu.RUnlock() - return n -} - -// Delete all items from the cache. -func (c *cache) Flush() { - c.mu.Lock() - c.items = map[string]Item{} - c.mu.Unlock() -} - -type janitor struct { - Interval time.Duration - stop chan bool -} - -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 stopJanitor(c *{{ .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) *{{ .Cache }} { - c := newCache(de, m) - // 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 }}{c} - if ci > 0 { - runJanitor(c, ci) - // 如果C被回收了,但是c不会,因为stopJanitor是一个一直运行的 - // goroutine对c一直有引用不会被回收,所以加一个Finalizer来停掉 - // 这个goroutine然后让c被回收. - runtime.SetFinalizer(C, stopJanitor) - } - return C -} - -// Return a new cache with a given default expiration duration and cleanup -// interval. If the expiration duration is less than one (or NoExpiration), -// the items in the cache never expire (by default), and must be deleted -// manually. If the cleanup interval is less than one, expired items are not -// deleted from the cache before calling c.DeleteExpired(). -func {{ .Cache }}New(defaultExpiration, cleanupInterval time.Duration) *{{ .Cache }}{ - items := make(map[string]Item) - return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) -}` - func fatal(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) os.Exit(1) } +func packageDir() string { + _, filename, _, ok := runtime.Caller(0) + if !ok { + panic("No caller information") + } + return path.Dir(filename) +} + func main() { keyType := flag.String("k", "", "key type") valueType := flag.String("v", "", "value type") @@ -343,7 +49,7 @@ func main() { fatal(err) } defer f.Close() - tpl, err := template.New("cachemap").Parse(cachemapTemplate) + tpl, err := template.New("cache.tmpl").ParseFiles(filepath.Join(packageDir(), "cache.tmpl")) if err != nil { fatal(err) } diff --git a/sharded.go b/sharded.go index b1ab771..4d360ac 100644 --- a/sharded.go +++ b/sharded.go @@ -66,19 +66,19 @@ func (sc *shardedCache) bucket(k string) *cache { return sc.cs[djb33(sc.seed, k)%sc.m] } -func (sc *shardedCache) Set(k string, x ValueType, d time.Duration) { +func (sc *shardedCache) Set(k string, x ValueType_tpl, d time.Duration) { sc.bucket(k).Set(k, x, d) } -func (sc *shardedCache) Add(k string, x ValueType, d time.Duration) error { +func (sc *shardedCache) Add(k string, x ValueType_tpl, d time.Duration) error { return sc.bucket(k).Add(k, x, d) } -func (sc *shardedCache) Replace(k string, x ValueType, d time.Duration) error { +func (sc *shardedCache) Replace(k string, x ValueType_tpl, d time.Duration) error { return sc.bucket(k).Replace(k, x, d) } -func (sc *shardedCache) Get(k string) *ValueType { +func (sc *shardedCache) Get(k string) (ValueType_tpl, bool) { return sc.bucket(k).Get(k) } diff --git a/sharded_test.go b/sharded_test.go index 01e1e11..c462f02 100644 --- a/sharded_test.go +++ b/sharded_test.go @@ -29,7 +29,7 @@ var shardedKeys = []string{ func TestShardedCache(t *testing.T) { tc := unexportedNewSharded(DefaultExpiration, 0, 13) for _, v := range shardedKeys { - tc.Set(v, ValueType(1), DefaultExpiration) + tc.Set(v, ValueType_tpl(1), DefaultExpiration) } } @@ -44,7 +44,7 @@ func BenchmarkShardedCacheGetNotExpiring(b *testing.B) { func benchmarkShardedCacheGet(b *testing.B, exp time.Duration) { b.StopTimer() tc := unexportedNewSharded(exp, 0, 10) - tc.Set("foobarba", ValueType(1), DefaultExpiration) + tc.Set("foobarba", ValueType_tpl(1), DefaultExpiration) b.StartTimer() for i := 0; i < b.N; i++ { tc.Get("foobarba") @@ -67,15 +67,16 @@ func benchmarkShardedCacheGetManyConcurrent(b *testing.B, exp time.Duration) { for i := 0; i < n; i++ { k := "foo" + strconv.Itoa(i) keys[i] = k - tsc.Set(k, ValueType(1), DefaultExpiration) + tsc.Set(k, ValueType_tpl(1), DefaultExpiration) } each := b.N / n wg := new(sync.WaitGroup) wg.Add(n) for _, v := range keys { + x := v go func() { for j := 0; j < each; j++ { - tsc.Get(v) + tsc.Get(x) } wg.Done() }() diff --git a/valtyp.go b/valtyp.go index b31a4d3..905d600 100644 --- a/valtyp.go +++ b/valtyp.go @@ -1,3 +1,3 @@ package cache -type ValueType int +type ValueType_tpl int From 5b0629018805a5bcbd8ad9ad252343a88bb8cb09 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Sat, 27 Aug 2016 19:35:01 +0800 Subject: [PATCH 05/14] Support zerovalue and fix tests Signed-off-by: Peng Gao --- cache_test.go | 21 +++++++------ cachemap/cache.tmpl | 3 +- cachemap/main.go | 74 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/cache_test.go b/cache_test.go index c1a4d80..904a2df 100644 --- a/cache_test.go +++ b/cache_test.go @@ -198,21 +198,22 @@ func TestFlush(t *testing.T) { } func TestOnEvicted(t *testing.T) { - tc := New_tpl(Attr_tpl{ + works := false + var tc *Cache_tpl + tc = New_tpl(Attr_tpl{ DefaultExpiration: DefaultExpiration, DefaultCleanupInterval: 0, + OnEvicted: func(k string, v ValueType_tpl) { + if k == "foo" && v == 3 { + works = true + } + tc.Set("bar", 4, DefaultExpiration) + }, }) tc.Set("foo", 3, DefaultExpiration) - if tc.onEvicted != nil { - t.Fatal("tc.onEvicted is not nil") + if tc.onEvicted == nil { + t.Fatal("tc.onEvicted is nil") } - works := false - tc.OnEvicted(func(k string, v ValueType_tpl) { - if k == "foo" && v == 3 { - works = true - } - tc.Set("bar", 4, DefaultExpiration) - }) tc.Delete("foo") x, _ := tc.Get("bar") if !works { diff --git a/cachemap/cache.tmpl b/cachemap/cache.tmpl index 26dc373..351c3c0 100644 --- a/cachemap/cache.tmpl +++ b/cachemap/cache.tmpl @@ -182,8 +182,7 @@ func (c *cache) delete(k string) ({{.ValueType}}, bool) { delete(c.items, k) return v.Object, true } - //TODO: zeroValue - return 0, false + return {{ .ZeroValue }}, false } func (c *cache) deleteFast(k string) { diff --git a/cachemap/main.go b/cachemap/main.go index 3c2dd4e..e7f480d 100644 --- a/cachemap/main.go +++ b/cachemap/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "go/ast" "go/parser" "go/token" "os" @@ -25,6 +26,61 @@ func packageDir() string { return path.Dir(filename) } +// find value of ident 'grammar' in GenDecl. +func findInGenDecl(genDecl *ast.GenDecl, grammarName string) string { + for _, spec := range genDecl.Specs { + valueSpec, ok := spec.(*ast.TypeSpec) + if ok { + // type ident + ident, ok := valueSpec.Type.(*ast.Ident) + if ok { + return ident.Name + } + } + } + return "" +} + +func findInDecl(decl ast.Decl, grammarName string) string { + genDecl, ok := decl.(*ast.GenDecl) + if ok { + g := findInGenDecl(genDecl, grammarName) + if g != "" { + return g + } + } + return "" +} + +// zeroValue returns literal zero value. +func zeroValue(s string) string { + // TODO: support func type. + switch s { + case "string": + return "\"\"" + case "int", "uint", "int64", "uint64", "uint32", "int32", "int16", + "uint16", "int8", "uint8", "byte", "rune", "float64", "float32", + "complex64", "complex32", "uintptr": + return "0" + case "slice": + return "nil" + default: + if s[0] == '*' { // Pointer + return "nil" + } + return s + "{}" + } +} + +// TODO: support more builtin types +func builtin(s string) bool { + switch s { + case "string": + return true + } + return false +} + func main() { keyType := flag.String("k", "", "key type") valueType := flag.String("v", "", "value type") @@ -40,10 +96,23 @@ func main() { if err != nil { fatal(err) } - var packageName string - for name := range pkgs { + packageName := "main" + typeName := "" + for name, pkg := range pkgs { packageName = name + for _, f := range pkg.Files { + for _, decl := range f.Decls { + typeName = findInDecl(decl, *valueType) + } + } } + if typeName == "" && !builtin(*valueType) { + fatal(fmt.Errorf("found no definition of %s in files\n", *valueType)) + } + if typeName == "" { + typeName = *valueType + } + zeroTypeValue := zeroValue(typeName) f, err := os.OpenFile(fmt.Sprintf("%s2%s_cachemap.go", *keyType, *valueType), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { fatal(err) @@ -59,6 +128,7 @@ func main() { "ValueType": *valueType, "PackageName": packageName, "Cache": fmt.Sprintf("String2%sCache", *valueType), + "ZeroValue": zeroTypeValue, }, ) if err != nil { From 3cceb4fab09ed58245a22b279df1d28fee763157 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Sat, 27 Aug 2016 20:00:58 +0800 Subject: [PATCH 06/14] Update README, ci, pa Signed-off-by: Peng Gao --- .pullapprove.yml | 9 +++++++++ .travis.yml | 8 ++++++++ README.md | 17 ++++++++--------- 3 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 .pullapprove.yml create mode 100644 .travis.yml diff --git a/.pullapprove.yml b/.pullapprove.yml new file mode 100644 index 0000000..f72d0cb --- /dev/null +++ b/.pullapprove.yml @@ -0,0 +1,9 @@ +approve_by_comment: true +approve_regex: ^LGTM +reject_regex: ^Rejected +reset_on_push: true +reviewers: + members: + - ggaaooppeenngg + name: pullapprove + required: 1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9305d77 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: go + +go: + - tip + +script: + - go test -v -coverprofile=coverage.txt -covermode=atomic + diff --git a/README.md b/README.md index bd47807..8229ebb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ -# go-cache +[![Build Status](https://travis-ci.org/ggaaooppeenngg/cachemap.svg?branch=master)](https://travis-ci.org/ggaaooppeenngg/cachemap) +[![Go Report Card](https://goreportcard.com/badge/github.com/ggaaooppeenngg/cachemap)](https://goreportcard.com/report/github.com/ggaaooppeenngg/cachemap) +[![GoDoc](https://godoc.org/github.com/ggaaooppeenngg/cachemap?status.svg)](https://godoc.org/github.com/ggaaooppeenngg/cachemap) +# cachemap -go-cache is an in-memory key:value store/cache similar to memcached that is +cachemap is an in-memory key:value store/cache similar to memcached that is suitable for applications running on a single machine. Its major advantage is that, being essentially a thread-safe `map[string]interface{}` with expiration times, it doesn't need to serialize or transmit its contents over the network. @@ -8,21 +11,21 @@ times, it doesn't need to serialize or transmit its contents over the network. Any object can be stored, for a given duration or forever, and the cache can be safely used by multiple goroutines. -Although go-cache isn't meant to be used as a persistent datastore, the entire +Although cachemap isn't meant to be used as a persistent datastore, the entire cache can be saved to and loaded from a file (using `c.Items()` to retrieve the items map to serialize, and `NewFrom()` to create a cache from a deserialized one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats.) ### Installation -`go get github.com/patrickmn/go-cache` +`go get github.com/ggaaooppeenngg/cachemap` ### Usage ```go import ( "fmt" - "github.com/patrickmn/go-cache" + "github.com/patrickmn/cachemap" "time" ) @@ -102,10 +105,6 @@ one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats } ``` -### Reference - -`godoc` or [http://godoc.org/github.com/patrickmn/go-cache](http://godoc.org/github.com/patrickmn/go-cache) - ### Benchmark | benchmark\package | go-cache | cachemap | From ebc1ab826e88e9903bc3ea995ef04fdc1d202951 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Mon, 29 Aug 2016 18:58:22 +0800 Subject: [PATCH 07/14] Add decrement and increment for numberic types Signed-off-by: Peng Gao --- cache.go | 38 +++++++++++++++++++------ cache_test.go | 40 ++++++++++++++++++++++++-- cachemap/cache.tmpl | 60 ++++++++++++++++++++++++--------------- cachemap/main.go | 69 +++++++++++++++++++++++++++++++++------------ sharded.go | 4 +-- 5 files changed, 157 insertions(+), 54 deletions(-) diff --git a/cache.go b/cache.go index 134f9c7..55af84a 100644 --- a/cache.go +++ b/cache.go @@ -16,9 +16,9 @@ type Attr_tpl struct { // not when it is overwritten.) Set to nil to disable. OnEvicted func(k string, v ValueType_tpl) - DefaultCleanupInterval time.Duration // default clean interval - DefaultExpiration time.Duration // default expiration duration - Size int64 // initial size of map + DefaultCleanupInterval time.Duration // Default clean interval, this is a time interval to cleanup expired items + DefaultExpiration time.Duration // Default expiration duration + Size int64 // Initial size of map } // Item struct @@ -27,7 +27,7 @@ type Item struct { Expiration int64 } -// Expired Returns true if the item has expired, if valid Expiration is set. +// Expired returns true if the item has expired. func (item Item) Expired() bool { return item.Expiration != 0 && time.Now().UnixNano() > item.Expiration } @@ -37,7 +37,7 @@ const ( NoExpiration time.Duration = -1 // DefaultExpiration is for use with functions that take an // expiration time. Equivalent to passing in the same expiration - // duration as was given to New() or NewFrom() when the cache was + // duration as was given to New() when the cache was // created (e.g. 5 minutes.) DefaultExpiration time.Duration = 0 ) @@ -143,13 +143,23 @@ func (c *cache) get(k string) (*ValueType_tpl, bool) { return &item.Object, true } +// MARK_Numberic_tpl_begin + // Increment an item of type int, int8, int16, int32, int64, uintptr, uint, // uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the // 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. -// TODO: Increment for numberic type. -func (c *cache) Increment(k string, n int64) error { +func (c *cache) Increment(k string, n ValueType_tpl) error { + c.mu.Lock() + v, found := c.items[k] + if !found || v.Expired() { + c.mu.Unlock() + return fmt.Errorf("Item %s not found", k) + } + v.Object += n + c.items[k] = v + c.mu.Unlock() return nil } @@ -158,13 +168,23 @@ func (c *cache) Increment(k string, n int64) 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. -// TODO: Decrement -func (c *cache) Decrement(k string, n int64) error { +func (c *cache) 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() + v, found := c.items[k] + if !found || v.Expired() { + c.mu.Unlock() + return fmt.Errorf("Item not found") + } + v.Object -= n + c.items[k] = v + c.mu.Unlock() return nil } +// 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) { // fast path diff --git a/cache_test.go b/cache_test.go index 904a2df..94554a9 100644 --- a/cache_test.go +++ b/cache_test.go @@ -110,8 +110,42 @@ func TestCacheTimes(t *testing.T) { } } -// TODO: test increment. -func TestIncrementWithInt(t *testing.T) { +func TestIncrement(t *testing.T) { + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) + tc.Set("tint", 1, DefaultExpiration) + err := tc.Increment("tint", 2) + if err != nil { + t.Error("Error incrementing:", err) + } + x, found := tc.Get("tint") + if !found { + t.Error("tint was not found") + } + if x != 3 { + t.Error("tint is not 3:", x) + } +} + +func TestDecrement(t *testing.T) { + tc := New_tpl(Attr_tpl{ + DefaultExpiration: DefaultExpiration, + DefaultCleanupInterval: 0, + }) + tc.Set("int", 5, DefaultExpiration) + err := tc.Decrement("int", 2) + if err != nil { + t.Error("Error decrementing:", err) + } + x, found := tc.Get("int") + if !found { + t.Error("int was not found") + } + if x != 3 { + t.Error("int is not 3:", x) + } } func TestAdd(t *testing.T) { @@ -481,7 +515,7 @@ func BenchmarkIncrementInt(b *testing.B) { tc.Set("foo", 0, DefaultExpiration) b.StartTimer() for i := 0; i < b.N; i++ { - // tc.IncrementInt("foo", 1) + tc.Increment("foo", 1) } } diff --git a/cachemap/cache.tmpl b/cachemap/cache.tmpl index 351c3c0..b77273e 100644 --- a/cachemap/cache.tmpl +++ b/cachemap/cache.tmpl @@ -1,4 +1,6 @@ -package {{.PackageName}} +package {{.PackageName}} + +// The package is used as a template, don't use it directly! import ( "fmt" @@ -9,10 +11,14 @@ import ( // Attr is cachmap attribute type {{.ValueType}}CacheAttr struct { - OnEvicted func(k string, v {{.ValueType}}) // called when k evicted if set - DefaultCleanupInterval time.Duration // default clean interval - DefaultExpiration time.Duration // default expiration duration - Size int64 // inital size of map + // An (optional) function that is called with the key and value when an + // item is evicted from the cache. (Including when it is deleted manually, but + // not when it is overwritten.) Set to nil to disable. + OnEvicted func(k string, v {{.ValueType}}) + + DefaultCleanupInterval time.Duration // Default clean interval, this is a time interval to cleanup expired items + DefaultExpiration time.Duration // Default expiration duration + Size int64 // Initial size of map } // Item struct @@ -21,7 +27,7 @@ type Item struct { Expiration int64 } -// Expired Returns true if the item has expired, if valid Expiration is set. +// Expired returns true if the item has expired. func (item Item) Expired() bool { return item.Expiration != 0 && time.Now().UnixNano() > item.Expiration } @@ -31,7 +37,7 @@ const ( NoExpiration time.Duration = -1 // DefaultExpiration is for use with functions that take an // expiration time. Equivalent to passing in the same expiration - // duration as was given to New() or NewFrom() when the cache was + // duration as was given to New() when the cache was // created (e.g. 5 minutes.) DefaultExpiration time.Duration = 0 ) @@ -137,13 +143,23 @@ func (c *cache) get(k string) (*{{.ValueType}}, bool) { return &item.Object, true } +{{ if call .IsNumberic .RealType }} + // Increment an item of type int, int8, int16, int32, int64, uintptr, uint, // uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the // 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. -// TODO: Increment for numberic type. -func (c *cache) Increment(k string, n int64) error { +func (c *cache) Increment(k string, n {{.ValueType}}) error { + c.mu.Lock() + v, found := c.items[k] + if !found || v.Expired() { + c.mu.Unlock() + return fmt.Errorf("Item %s not found", k) + } + v.Object += n + c.items[k] = v + c.mu.Unlock() return nil } @@ -152,13 +168,22 @@ func (c *cache) Increment(k string, n int64) 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. -// TODO: Decrement -func (c *cache) Decrement(k string, n int64) error { +func (c *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() + v, found := c.items[k] + if !found || v.Expired() { + c.mu.Unlock() + return fmt.Errorf("Item not found") + } + c.items[k] = v + c.mu.Unlock() return nil } +{{end}} + // Delete an item from the cache. Does nothing if the key is not in the cache. func (c *cache) Delete(k string) { // fast path @@ -182,7 +207,8 @@ func (c *cache) delete(k string) ({{.ValueType}}, bool) { delete(c.items, k) return v.Object, true } - return {{ .ZeroValue }}, false + //TODO: zeroValue + return 0, false } func (c *cache) deleteFast(k string) { @@ -227,16 +253,6 @@ func (c *cache) DeleteExpired() { } } -// Sets an (optional) function that is called with the key and value when an -// item is evicted from the cache. (Including when it is deleted manually, but -// not when it is overwritten.) Set to nil to disable. -// 这里加锁没有意义 -func (c *cache) OnEvicted(f func(string, {{.ValueType}})) { - c.mu.Lock() - c.onEvicted = f - c.mu.Unlock() -} - // 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 { diff --git a/cachemap/main.go b/cachemap/main.go index e7f480d..44643b0 100644 --- a/cachemap/main.go +++ b/cachemap/main.go @@ -10,6 +10,7 @@ import ( "path" "path/filepath" "runtime" + "strings" "text/template" ) @@ -26,25 +27,27 @@ func packageDir() string { return path.Dir(filename) } -// find value of ident 'grammar' in GenDecl. -func findInGenDecl(genDecl *ast.GenDecl, grammarName string) string { +// TODO: parse type if type is also not builtin type. +// find literal value of type. +func findInGenDecl(genDecl *ast.GenDecl, valueName string) string { for _, spec := range genDecl.Specs { valueSpec, ok := spec.(*ast.TypeSpec) if ok { - // type ident - ident, ok := valueSpec.Type.(*ast.Ident) - if ok { - return ident.Name + if ok && valueSpec.Name.Name == valueName { + indent, ok := valueSpec.Type.(*ast.Ident) + if ok { + return indent.Name + } } } } return "" } -func findInDecl(decl ast.Decl, grammarName string) string { +func findInDecl(decl ast.Decl, valueName string) string { genDecl, ok := decl.(*ast.GenDecl) if ok { - g := findInGenDecl(genDecl, grammarName) + g := findInGenDecl(genDecl, valueName) if g != "" { return g } @@ -56,27 +59,51 @@ func findInDecl(decl ast.Decl, grammarName string) string { func zeroValue(s string) string { // TODO: support func type. switch s { + case "bool": + return "false" case "string": return "\"\"" case "int", "uint", "int64", "uint64", "uint32", "int32", "int16", "uint16", "int8", "uint8", "byte", "rune", "float64", "float32", "complex64", "complex32", "uintptr": return "0" - case "slice": - return "nil" default: - if s[0] == '*' { // Pointer + if s[0] == '*' || // Pointer + strings.Index(s, "map") == 0 || // map + strings.Index(s, "chan") == 0 || // chan + strings.Index(s, "[]") == 0 { // slice return "nil" } return s + "{}" } } -// TODO: support more builtin types -func builtin(s string) bool { - switch s { - case "string": - return true +var builtinTypes = []string{ + "bool", + "string", + + "int", "int8", "int16", "int32", "int64", // numbericType + "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", + "float32", "float64", + "complex64", "complex128", + "byte", + "rune", +} + +func isNumberic(s string) bool { + for _, v := range builtinTypes[2:] { // 2 is beginning of numberic types in builtinTypes. + if v == s { + return true + } + } + return false +} + +func isBuiltin(s string) bool { + for _, v := range builtinTypes { + if v == s { + return true + } } return false } @@ -106,12 +133,13 @@ func main() { } } } - if typeName == "" && !builtin(*valueType) { + if typeName == "" && !isBuiltin(*valueType) { fatal(fmt.Errorf("found no definition of %s in files\n", *valueType)) } if typeName == "" { typeName = *valueType } + fmt.Println("real", typeName, "value", *valueType) zeroTypeValue := zeroValue(typeName) f, err := os.OpenFile(fmt.Sprintf("%s2%s_cachemap.go", *keyType, *valueType), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { @@ -122,13 +150,18 @@ func main() { if err != nil { fatal(err) } + if isBuiltin(*valueType) { + *valueType = strings.Title(*valueType) + } err = tpl.Execute( f, - map[string]string{ + map[string]interface{}{ "ValueType": *valueType, + "RealType": typeName, "PackageName": packageName, "Cache": fmt.Sprintf("String2%sCache", *valueType), "ZeroValue": zeroTypeValue, + "IsNumberic": isNumberic, }, ) if err != nil { diff --git a/sharded.go b/sharded.go index 4d360ac..1cd80ef 100644 --- a/sharded.go +++ b/sharded.go @@ -82,11 +82,11 @@ func (sc *shardedCache) Get(k string) (ValueType_tpl, bool) { return sc.bucket(k).Get(k) } -func (sc *shardedCache) Increment(k string, n int64) error { +func (sc *shardedCache) Increment(k string, n ValueType_tpl) error { return sc.bucket(k).Increment(k, n) } -func (sc *shardedCache) Decrement(k string, n int64) error { +func (sc *shardedCache) Decrement(k string, n ValueType_tpl) error { return sc.bucket(k).Decrement(k, n) } From 4378c9a40edcd2db5eda9f73ea3fba9fdede5024 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Wed, 31 Aug 2016 12:21:05 +0800 Subject: [PATCH 08/14] Fix type finding bugs and add zero vaule for func 1. In the main loop to find typeName, it will continue find type if a type has been already found, which will make empty string overwite it latter, so if it is found first time, assuming this is the type we want, and break out the loop. 2. Add support for zero value for func type. 3. If the type not a builtin type (*ast.Ident), assuming it is a composite structure, like *ast.StructType. Signed-off-by: Peng Gao --- cachemap/main.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cachemap/main.go b/cachemap/main.go index 44643b0..fc63fd7 100644 --- a/cachemap/main.go +++ b/cachemap/main.go @@ -37,6 +37,9 @@ func findInGenDecl(genDecl *ast.GenDecl, valueName string) string { indent, ok := valueSpec.Type.(*ast.Ident) if ok { return indent.Name + } else { + // For other types like StructType + return valueName } } } @@ -71,7 +74,8 @@ func zeroValue(s string) string { if s[0] == '*' || // Pointer strings.Index(s, "map") == 0 || // map strings.Index(s, "chan") == 0 || // chan - strings.Index(s, "[]") == 0 { // slice + strings.Index(s, "[]") == 0 || // slice + strings.Index(s, "func") == 0 { // func return "nil" } return s + "{}" @@ -125,11 +129,15 @@ func main() { } packageName := "main" typeName := "" +FIND: for name, pkg := range pkgs { packageName = name for _, f := range pkg.Files { for _, decl := range f.Decls { typeName = findInDecl(decl, *valueType) + if typeName != "" { + break FIND + } } } } From f7f14e07e48f1705d94e72db9c880ef052178ce2 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Wed, 31 Aug 2016 14:04:57 +0800 Subject: [PATCH 09/14] Support zero value in template Signed-off-by: Peng Gao --- cachemap/cache.tmpl | 5 ++--- cachemap/main.go | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cachemap/cache.tmpl b/cachemap/cache.tmpl index b77273e..1e10eaa 100644 --- a/cachemap/cache.tmpl +++ b/cachemap/cache.tmpl @@ -129,7 +129,7 @@ func (c *cache) Get(k string) ({{.ValueType}}, bool) { // TODO: inline time.Now implementation if !found || item.Expiration > 0 && time.Now().UnixNano() > item.Expiration { c.mu.RUnlock() - return {{.ValueType}}(0), false + return {{.ZeroValue}}, false } c.mu.RUnlock() return item.Object, true @@ -207,8 +207,7 @@ func (c *cache) delete(k string) ({{.ValueType}}, bool) { delete(c.items, k) return v.Object, true } - //TODO: zeroValue - return 0, false + return {{.ZeroValue}}, false } func (c *cache) deleteFast(k string) { diff --git a/cachemap/main.go b/cachemap/main.go index fc63fd7..deab2ba 100644 --- a/cachemap/main.go +++ b/cachemap/main.go @@ -147,7 +147,6 @@ FIND: if typeName == "" { typeName = *valueType } - fmt.Println("real", typeName, "value", *valueType) zeroTypeValue := zeroValue(typeName) f, err := os.OpenFile(fmt.Sprintf("%s2%s_cachemap.go", *keyType, *valueType), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { From f26e1d0b065cb4ca9b762995ad7760e67b1339e2 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Thu, 1 Sep 2016 11:46:59 +0800 Subject: [PATCH 10/14] Fix #3, use put common code in individual a file If there are more than one cache map to be generated for different types, a redeclaration error will occur, to avoid this error, put common part in a single file. Signed-off-by: Peng Gao --- cache.go | 10 ---------- cachemap/cache.tmpl | 10 ---------- cachemap/const.tmpl | 19 +++++++++++++++++++ cachemap/main.go | 18 +++++++++++++++++- const.go | 17 +++++++++++++++++ 5 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 cachemap/const.tmpl create mode 100644 const.go diff --git a/cache.go b/cache.go index 55af84a..83ca900 100644 --- a/cache.go +++ b/cache.go @@ -32,16 +32,6 @@ func (item Item) Expired() bool { return item.Expiration != 0 && time.Now().UnixNano() > item.Expiration } -const ( - // NoExpiration is for use with functions that take no expiration time. - NoExpiration time.Duration = -1 - // DefaultExpiration is for use with functions that take an - // expiration time. Equivalent to passing in the same expiration - // duration as was given to New() when the cache was - // created (e.g. 5 minutes.) - DefaultExpiration time.Duration = 0 -) - // Cache struct type Cache_tpl struct { *cache diff --git a/cachemap/cache.tmpl b/cachemap/cache.tmpl index 1e10eaa..a7ec550 100644 --- a/cachemap/cache.tmpl +++ b/cachemap/cache.tmpl @@ -32,16 +32,6 @@ func (item Item) Expired() bool { return item.Expiration != 0 && time.Now().UnixNano() > item.Expiration } -const ( - // NoExpiration is for use with functions that take no expiration time. - NoExpiration time.Duration = -1 - // DefaultExpiration is for use with functions that take an - // expiration time. Equivalent to passing in the same expiration - // duration as was given to New() when the cache was - // created (e.g. 5 minutes.) - DefaultExpiration time.Duration = 0 -) - // Cache struct type {{.ValueType}}Cache struct { *cache diff --git a/cachemap/const.tmpl b/cachemap/const.tmpl new file mode 100644 index 0000000..8b61a4a --- /dev/null +++ b/cachemap/const.tmpl @@ -0,0 +1,19 @@ +package {{.PackageName}} + +import ( + "time" +) + +// To avoid redecleration, put common code in this file. + +const ( + // NoExpiration is for use with functions that take no expiration time. + NoExpiration time.Duration = -1 + // DefaultExpiration is for use with functions that take an + // expiration time. Equivalent to passing in the same expiration + // duration as was given to New() when the cache was + // created (e.g. 5 minutes.) + DefaultExpiration time.Duration = 0 +) + + diff --git a/cachemap/main.go b/cachemap/main.go index deab2ba..18b4ddf 100644 --- a/cachemap/main.go +++ b/cachemap/main.go @@ -157,7 +157,7 @@ FIND: if err != nil { fatal(err) } - if isBuiltin(*valueType) { + if !isBuiltin(*valueType) { *valueType = strings.Title(*valueType) } err = tpl.Execute( @@ -174,4 +174,20 @@ FIND: if err != nil { fatal(err) } + constFile, err := os.OpenFile(fmt.Sprintf("cachemap_const.go"), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + fatal(err) + } + defer constFile.Close() + constTpl, err := template.New("const.tmpl").ParseFiles(filepath.Join(packageDir(), "const.tmpl")) + if err != nil { + fatal(err) + } + err = constTpl.Execute(constFile, + map[string]interface{}{ + "PackageName": packageName, + }) + if err != nil { + fatal(err) + } } diff --git a/const.go b/const.go new file mode 100644 index 0000000..22b8518 --- /dev/null +++ b/const.go @@ -0,0 +1,17 @@ +package cache + +import ( + "time" +) + +// To avoid redecleration errors, put common code in this file. + +const ( + // NoExpiration is for use with functions that take no expiration time. + NoExpiration time.Duration = -1 + // DefaultExpiration is for use with functions that take an + // expiration time. Equivalent to passing in the same expiration + // duration as was given to New() when the cache was + // created (e.g. 5 minutes.) + DefaultExpiration time.Duration = 0 +) From 884405c431ca1694a5a5cb4bf537b047ef6e3104 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Thu, 1 Sep 2016 14:03:22 +0800 Subject: [PATCH 11/14] 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 } From 64597b577e94ee3dc3db37a9aca8d16b0df8cc34 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Wed, 9 Nov 2016 14:33:00 +0800 Subject: [PATCH 12/14] Update travis Signed-off-by: Peng Gao --- .travis.yml | 9 +++++++-- README.md | 8 +++++--- cache_test.go | 1 - 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9305d77..bac1a1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,11 @@ language: go go: - tip -script: - - go test -v -coverprofile=coverage.txt -covermode=atomic +before_script: + - go get github.com/axw/gocov/gocov + - go get github.com/mattn/goveralls + - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi + +script: + - $HOME/gopath/bin/goveralls -service=travis-ci diff --git a/README.md b/README.md index 8229ebb..570380c 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ cachemap is an in-memory key:value store/cache similar to memcached that is suitable for applications running on a single machine. Its major advantage is -that, being essentially a thread-safe `map[string]interface{}` with expiration -times, it doesn't need to serialize or transmit its contents over the network. +that, being essentially a thread-safe map with expirationtimes, it doesn't +need to serialize or transmit its contents over the network. Any object can be stored, for a given duration or forever, and the cache can be safely used by multiple goroutines. @@ -22,10 +22,12 @@ one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats ### Usage +run `cachemap -k string -v string` + ```go import ( "fmt" - "github.com/patrickmn/cachemap" + "github.com/ggaaooppeengg/cachemap" "time" ) diff --git a/cache_test.go b/cache_test.go index 94554a9..2e406fe 100644 --- a/cache_test.go +++ b/cache_test.go @@ -506,7 +506,6 @@ func BenchmarkRWMutexMapSetDeleteSingleLock(b *testing.B) { } func BenchmarkIncrementInt(b *testing.B) { - b.Skip() b.StopTimer() tc := New_tpl(Attr_tpl{ DefaultExpiration: DefaultExpiration, From 266c1c3f3e16810776ca76215eaf256b4ced2994 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Wed, 9 Nov 2016 15:24:47 +0800 Subject: [PATCH 13/14] Update coverage badge Signed-off-by: Peng Gao --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 570380c..ba28064 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Build Status](https://travis-ci.org/ggaaooppeenngg/cachemap.svg?branch=master)](https://travis-ci.org/ggaaooppeenngg/cachemap) +[![Coverage Status](https://coveralls.io/repos/github/ggaaooppeenngg/cachemap/badge.svg?branch=sharded-dev)](https://coveralls.io/github/ggaaooppeenngg/cachemap?branch=sharded-dev) [![Go Report Card](https://goreportcard.com/badge/github.com/ggaaooppeenngg/cachemap)](https://goreportcard.com/report/github.com/ggaaooppeenngg/cachemap) [![GoDoc](https://godoc.org/github.com/ggaaooppeenngg/cachemap?status.svg)](https://godoc.org/github.com/ggaaooppeenngg/cachemap) # cachemap From e4b4bf33ab7775b4d33f87e360c34b8fc906680e Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Mon, 14 Nov 2016 11:53:45 +0800 Subject: [PATCH 14/14] Update README.md Signed-off-by: Peng Gao --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ba28064..575b54c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ cache can be saved to and loaded from a file (using `c.Items()` to retrieve the items map to serialize, and `NewFrom()` to create a cache from a deserialized one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats.) +### Notice + +This is a repo in development, so I leave a implementation with suffix "\_tpl" in `*.go` and the real generator main package in `cachemap` directory,to generate a template for generic usage in main package, you can try to run script like `cat cache.go | sed 's/ValueType_tpl/{{.ValueType}}/g' | sed 's/New_tpl/New{{.ValueType}}Cache/g' | sed 's/Attr_tpl/{{.ValueType}}CacheAttr/g' | sed 's/Item_tpl/{{.ValueType}}Item/g' |sed 's/Cache_tpl/{{.ValueType}}Cache/g'|sed 's/cache_tpl/{{.ValueType}}cache/g' | sed 's/package\ cache/package\ {{.PackageName}} /g' | sed 's|// MARK_Numberic_tpl_begin|{{ if call .IsNumberic .RealType }}|g' | sed 's|// MARK_Numberic_tpl_end|{{end}}|g' > cachemap/cache.tmpl` to replace "\_tpl" suffix with golang template variables in the main package. + ### Installation `go get github.com/ggaaooppeenngg/cachemap`