// Copyright 2014 beego Author. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cache import ( "encoding/json" "errors" "sync" "time" ) var ( // DefaultEvery means the clock time of recycling the expired cache items in memory. DefaultEvery = 60 // 1 minute ) // MemoryItem store memory cache item. type MemoryItem struct { val interface{} createdTime time.Time lifespan time.Duration } func (mi *MemoryItem) isExpire() bool { // 0 means forever if mi.lifespan == 0 { return false } return time.Now().Sub(mi.createdTime) > mi.lifespan } // MemoryCache is Memory cache adapter. // it contains a RW locker for safe map storage. type MemoryCache struct { sync.RWMutex dur time.Duration items map[string]*MemoryItem Every int // run an expiration check Every clock time } // NewMemoryCache returns a new MemoryCache. func NewMemoryCache() Cache { cache := MemoryCache{items: make(map[string]*MemoryItem)} return &cache } // Get cache from memory. // if non-existed or expired, return nil. func (bc *MemoryCache) Get(name string) interface{} { bc.RLock() defer bc.RUnlock() if itm, ok := bc.items[name]; ok { if itm.isExpire() { return nil } return itm.val } return nil } // GetMulti gets caches from memory. // if non-existed or expired, return nil. func (bc *MemoryCache) GetMulti(names []string) []interface{} { var rc []interface{} for _, name := range names { rc = append(rc, bc.Get(name)) } return rc } // Put cache to memory. // if lifespan is 0, it will be forever till restart. func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error { bc.Lock() defer bc.Unlock() bc.items[name] = &MemoryItem{ val: value, createdTime: time.Now(), lifespan: lifespan, } return nil } // Delete cache in memory. func (bc *MemoryCache) Delete(name string) error { bc.Lock() defer bc.Unlock() if _, ok := bc.items[name]; !ok { return errors.New("key not exist") } delete(bc.items, name) if _, ok := bc.items[name]; ok { return errors.New("delete key error") } return nil } // Incr increase cache counter in memory. // it supports int,int32,int64,uint,uint32,uint64. func (bc *MemoryCache) Incr(key string) error { bc.RLock() defer bc.RUnlock() itm, ok := bc.items[key] if !ok { return errors.New("key not exist") } switch itm.val.(type) { case int: itm.val = itm.val.(int) + 1 case int32: itm.val = itm.val.(int32) + 1 case int64: itm.val = itm.val.(int64) + 1 case uint: itm.val = itm.val.(uint) + 1 case uint32: itm.val = itm.val.(uint32) + 1 case uint64: itm.val = itm.val.(uint64) + 1 default: return errors.New("item val is not (u)int (u)int32 (u)int64") } return nil } // IncrBy increase cache counter in memory by num. // it supports int,int32,int64,uint,uint32,uint64. func (bc *MemoryCache) IncrBy(key string, num int) error { bc.RLock() defer bc.RUnlock() itm, ok := bc.items[key] if !ok { return errors.New("key not exist") } if num < 1 { return errors.New("increase num should be a positive number") } switch itm.val.(type) { case int: itm.val = itm.val.(int) + num case int32: itm.val = itm.val.(int32) + int32(num) case int64: itm.val = itm.val.(int64) + int64(num) case uint: itm.val = itm.val.(uint) + uint(num) case uint32: itm.val = itm.val.(uint32) + uint32(num) case uint64: itm.val = itm.val.(uint64) + uint64(num) default: return errors.New("item val is not (u)int (u)int32 (u)int64") } return nil } // Decr decrease counter in memory. func (bc *MemoryCache) Decr(key string) error { bc.RLock() defer bc.RUnlock() itm, ok := bc.items[key] if !ok { return errors.New("key not exist") } switch itm.val.(type) { case int: itm.val = itm.val.(int) - 1 case int64: itm.val = itm.val.(int64) - 1 case int32: itm.val = itm.val.(int32) - 1 case uint: if itm.val.(uint) > 0 { itm.val = itm.val.(uint) - 1 } else { return errors.New("item val is less than 0") } case uint32: if itm.val.(uint32) > 0 { itm.val = itm.val.(uint32) - 1 } else { return errors.New("item val is less than 0") } case uint64: if itm.val.(uint64) > 0 { itm.val = itm.val.(uint64) - 1 } else { return errors.New("item val is less than 0") } default: return errors.New("item val is not int int64 int32") } return nil } // DecrBy decrease counter in memory by num. func (bc *MemoryCache) DecrBy(key string, num int) error { bc.RLock() defer bc.RUnlock() itm, ok := bc.items[key] if !ok { return errors.New("key not exist") } if num < 1 { return errors.New("decrease num should be a positive number") } switch itm.val.(type) { case int: itm.val = itm.val.(int) - int(num) case int64: itm.val = itm.val.(int64) - int64(num) case int32: itm.val = itm.val.(int32) - int32(num) case uint: if itm.val.(uint) > 0 { itm.val = itm.val.(uint) - uint(num) } else { return errors.New("item val is less than 0") } case uint32: if itm.val.(uint32) > 0 { itm.val = itm.val.(uint32) - uint32(num) } else { return errors.New("item val is less than 0") } case uint64: if itm.val.(uint64) > 0 { itm.val = itm.val.(uint64) - uint64(num) } else { return errors.New("item val is less than 0") } default: return errors.New("item val is not int int64 int32") } return nil } // IsExist check cache exist in memory. func (bc *MemoryCache) IsExist(name string) bool { bc.RLock() defer bc.RUnlock() if v, ok := bc.items[name]; ok { return !v.isExpire() } return false } // ClearAll will delete all cache in memory. func (bc *MemoryCache) ClearAll() error { bc.Lock() defer bc.Unlock() bc.items = make(map[string]*MemoryItem) return nil } // StartAndGC start memory cache. it will check expiration in every clock time. func (bc *MemoryCache) StartAndGC(config string) error { var cf map[string]int json.Unmarshal([]byte(config), &cf) if _, ok := cf["interval"]; !ok { cf = make(map[string]int) cf["interval"] = DefaultEvery } dur := time.Duration(cf["interval"]) * time.Second bc.Every = cf["interval"] bc.dur = dur go bc.vaccuum() return nil } // check expiration. func (bc *MemoryCache) vaccuum() { if bc.Every < 1 { return } for { <-time.After(bc.dur) if bc.items == nil { return } if keys := bc.expiredKeys(); len(keys) != 0 { bc.clearItems(keys) } } } // expiredKeys returns key list which are expired. func (bc *MemoryCache) expiredKeys() (keys []string) { bc.RLock() defer bc.RUnlock() for key, itm := range bc.items { if itm.isExpire() { keys = append(keys, key) } } return } // clearItems removes all the items which key in keys. func (bc *MemoryCache) clearItems(keys []string) { bc.Lock() defer bc.Unlock() for _, key := range keys { delete(bc.items, key) } } func init() { Register("memory", NewMemoryCache) }