package gcache import ( "time" ) // SimpleCache has no clear priority for evict cache. It depends on key-value map order. type SimpleCache struct { baseCache items map[interface{}]*simpleItem } func newSimpleCache(cb *CacheBuilder) *SimpleCache { c := &SimpleCache{} buildCache(&c.baseCache, cb) c.init() c.loadGroup.cache = c return c } func (c *SimpleCache) init() { if c.size <= 0 { c.items = make(map[interface{}]*simpleItem) } else { c.items = make(map[interface{}]*simpleItem, c.size) } } // Set a new key-value pair func (c *SimpleCache) Set(key, value interface{}) error { c.mu.Lock() defer c.mu.Unlock() _, err := c.set(key, value) return err } // Set a new key-value pair with an expiration time func (c *SimpleCache) SetWithExpire(key, value interface{}, expiration time.Duration) error { c.mu.Lock() defer c.mu.Unlock() item, err := c.set(key, value) if err != nil { return err } t := c.clock.Now().Add(expiration) item.(*simpleItem).expiration = &t return nil } func (c *SimpleCache) set(key, value interface{}) (interface{}, error) { var err error if c.serializeFunc != nil { value, err = c.serializeFunc(key, value) if err != nil { return nil, err } } // Check for existing item item, ok := c.items[key] if ok { item.value = value } else { // Verify size not exceeded if (len(c.items) >= c.size) && c.size > 0 { c.evict(1) } item = &simpleItem{ clock: c.clock, value: value, } c.items[key] = item } if c.expiration != nil { t := c.clock.Now().Add(*c.expiration) item.expiration = &t } if c.addedFunc != nil { c.addedFunc(key, value) } return item, nil } // Get a value from cache pool using key if it exists. // If it dose not exists key and has LoaderFunc, // generate a value using `LoaderFunc` method returns value. func (c *SimpleCache) Get(key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { return c.getWithLoader(key, true) } return v, err } // GetIFPresent gets a value from cache pool using key if it exists. // If it dose not exists key, returns KeyNotFoundError. // And send a request which refresh value for specified key if cache object has LoaderFunc. func (c *SimpleCache) GetIFPresent(key interface{}) (interface{}, error) { v, err := c.get(key, false) if err == KeyNotFoundError { return c.getWithLoader(key, false) } return v, nil } func (c *SimpleCache) get(key interface{}, onLoad bool) (interface{}, error) { v, err := c.getValue(key, onLoad) if err != nil { return nil, err } if c.deserializeFunc != nil { return c.deserializeFunc(key, v) } return v, nil } func (c *SimpleCache) getValue(key interface{}, onLoad bool) (interface{}, error) { c.mu.Lock() item, ok := c.items[key] if ok { if !item.IsExpired(nil) { v := item.value c.mu.Unlock() if !onLoad { c.stats.IncrHitCount() } return v, nil } c.remove(key) } c.mu.Unlock() if !onLoad { c.stats.IncrMissCount() } return nil, KeyNotFoundError } func (c *SimpleCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) { if c.loaderExpireFunc == nil { return nil, KeyNotFoundError } value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { if e != nil { return nil, e } c.mu.Lock() defer c.mu.Unlock() item, err := c.set(key, v) if err != nil { return nil, err } if expiration != nil { t := c.clock.Now().Add(*expiration) item.(*simpleItem).expiration = &t } return v, nil }, isWait) if err != nil { return nil, err } return value, nil } func (c *SimpleCache) evict(count int) { now := c.clock.Now() current := 0 for key, item := range c.items { if current >= count { return } if item.expiration == nil || now.After(*item.expiration) { defer c.remove(key) current++ } } } // Has checks if key exists in cache func (c *SimpleCache) Has(key interface{}) bool { c.mu.RLock() defer c.mu.RUnlock() now := time.Now() return c.has(key, &now) } func (c *SimpleCache) has(key interface{}, now *time.Time) bool { item, ok := c.items[key] if !ok { return false } return !item.IsExpired(now) } // Remove removes the provided key from the cache. func (c *SimpleCache) Remove(key interface{}) bool { c.mu.Lock() defer c.mu.Unlock() return c.remove(key) } func (c *SimpleCache) remove(key interface{}) bool { item, ok := c.items[key] if ok { delete(c.items, key) if c.evictedFunc != nil { c.evictedFunc(key, item.value) } return true } return false } // Returns a slice of the keys in the cache. func (c *SimpleCache) keys() []interface{} { c.mu.RLock() defer c.mu.RUnlock() keys := make([]interface{}, len(c.items)) var i = 0 for k := range c.items { keys[i] = k i++ } return keys } // GetALL returns all key-value pairs in the cache. func (c *SimpleCache) GetALL(checkExpired bool) map[interface{}]interface{} { c.mu.RLock() defer c.mu.RUnlock() items := make(map[interface{}]interface{}, len(c.items)) now := time.Now() for k, item := range c.items { if !checkExpired || c.has(k, &now) { items[k] = item.value } } return items } // Keys returns a slice of the keys in the cache. func (c *SimpleCache) Keys(checkExpired bool) []interface{} { c.mu.RLock() defer c.mu.RUnlock() keys := make([]interface{}, 0, len(c.items)) now := time.Now() for k := range c.items { if !checkExpired || c.has(k, &now) { keys = append(keys, k) } } return keys } // Len returns the number of items in the cache. func (c *SimpleCache) Len(checkExpired bool) int { c.mu.RLock() defer c.mu.RUnlock() if !checkExpired { return len(c.items) } var length int now := time.Now() for k := range c.items { if c.has(k, &now) { length++ } } return length } // Completely clear the cache func (c *SimpleCache) Purge() { c.mu.Lock() defer c.mu.Unlock() if c.purgeVisitorFunc != nil { for key, item := range c.items { c.purgeVisitorFunc(key, item.value) } } c.init() } type simpleItem struct { clock Clock value interface{} expiration *time.Time } // IsExpired returns boolean value whether this item is expired or not. func (si *simpleItem) IsExpired(now *time.Time) bool { if si.expiration == nil { return false } if now == nil { t := si.clock.Now() now = &t } return si.expiration.Before(*now) }