|
|
- package cache
-
- import (
- "fmt"
- "runtime"
- "time"
-
- uuid "github.com/satori/go.uuid"
- )
-
- // Expired returns true if the item has expired
- func (item Item) Expired() bool {
- if item.Expiration == 0 {
- return false
- }
- return time.Now().UnixNano() > item.Expiration
- }
-
- // NewCache returns a simple in-memory cache
- func NewCache(defaultExp, cleanupInterval time.Duration) *Cache {
- items := make(map[uuid.UUID]Item)
- return newCacheWithJanitor(defaultExp, cleanupInterval, items)
- }
-
- func newCacheWithJanitor(defaultExp, cleanupInterval time.Duration, items map[uuid.UUID]Item) *Cache {
- cache := newCache(defaultExp, items)
- Cache := &Cache{cache}
- if cleanupInterval > 0 {
- runJanitor(cache, cleanupInterval)
- runtime.SetFinalizer(Cache, stopJanitor)
- }
- return Cache
- }
-
- func newCache(de time.Duration, m map[uuid.UUID]Item) *cache {
- if de == 0 {
- de = -1
- }
- c := &cache{
- defaultExpiration: de,
- items: m,
- }
- return c
- }
-
- // 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 uuid.UUID, x interface{}, 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 uuid.UUID, x interface{}, 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, replacing any existing item, using the default
- // expiration.
- func (c *cache) SetDefault(k uuid.UUID, x interface{}) {
- c.Set(k, x, DefaultExpiration)
- }
-
- // 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 uuid.UUID, x interface{}, 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 uuid.UUID, x interface{}, 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 uuid.UUID) (interface{}, bool) {
- c.mu.RLock()
- // "Inlining" of get and Expired
- item, found := c.items[k]
- if !found {
- c.mu.RUnlock()
- return nil, false
- }
- if item.Expiration > 0 {
- if time.Now().UnixNano() > item.Expiration {
- c.mu.RUnlock()
- return nil, false
- }
- }
- c.mu.RUnlock()
- return item.Object, true
- }
-
- // GetWithExpiration returns an item and its expiration time from the cache.
- // It returns the item or nil, the expiration time if one is set (if the item
- // never expires a zero value for time.Time is returned), and a bool indicating
- // whether the key was found.
- func (c *cache) GetWithExpiration(k uuid.UUID) (interface{}, time.Time, bool) {
- c.mu.RLock()
- // "Inlining" of get and Expired
- item, found := c.items[k]
- if !found {
- c.mu.RUnlock()
- return nil, time.Time{}, false
- }
-
- if item.Expiration > 0 {
- if time.Now().UnixNano() > item.Expiration {
- c.mu.RUnlock()
- return nil, time.Time{}, false
- }
-
- // Return the item and the expiration time
- c.mu.RUnlock()
- return item.Object, time.Unix(0, item.Expiration), true
- }
-
- // If expiration <= 0 (i.e. no expiration time set) then return the item
- // and a zeroed time.Time
- c.mu.RUnlock()
- return item.Object, time.Time{}, true
- }
-
- func (c *cache) get(k uuid.UUID) (interface{}, 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
- }
-
- func (c *cache) delete(k uuid.UUID) (interface{}, 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 uuid.UUID
- value interface{}
- }
-
- // 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(uuid.UUID, interface{})) {
- c.mu.Lock()
- c.onEvicted = f
- c.mu.Unlock()
- }
-
- // Delete all items from the cache.
- func (c *cache) Flush() {
- c.mu.Lock()
- c.items = map[uuid.UUID]Item{}
- c.mu.Unlock()
- }
-
- type janitor struct {
- Interval time.Duration
- stop chan bool
- }
-
- func (j *janitor) Run(c *cache) {
- 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,
- stop: make(chan bool),
- }
- c.janitor = j
- go j.Run(c)
- }
|