|
@@ -1,240 +1,280 @@
|
|
|
package cache
|
|
|
|
|
|
import (
|
|
|
- "runtime"
|
|
|
+ "context"
|
|
|
"sync"
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
-type CallCreate func(key interface{}) (value interface{}, created bool)
|
|
|
-type CallCreateNew func(key interface{}) (rKey, value interface{}, created bool)
|
|
|
-type CallCheck func(key, value interface{}, exists bool) (rKey, rValue interface{}, created bool)
|
|
|
-
|
|
|
-func NewMap(liveDuration time.Duration, maxSize int) *CacheMap {
|
|
|
- res := &CacheMap{&cacheMap{
|
|
|
- locker: new(sync.RWMutex),
|
|
|
- items: make(map[interface{}]*cacheMapItem),
|
|
|
- liveDuration: liveDuration,
|
|
|
- maxSize: maxSize,
|
|
|
- stopCleanerChan: make(chan byte),
|
|
|
- }}
|
|
|
- runtime.SetFinalizer(res, destroyCacheMap)
|
|
|
+const (
|
|
|
+ LiveInfinite = time.Duration(-1)
|
|
|
+)
|
|
|
+
|
|
|
+type CallCreate func(key any) (value any, created bool)
|
|
|
+type CallCreateNew func(key any) (rKey, value any, created bool)
|
|
|
+type CallCheck func(key, value any, exists bool) (rKey, rValue any, created bool)
|
|
|
+
|
|
|
+type item struct {
|
|
|
+ value any
|
|
|
+ expire int64
|
|
|
+}
|
|
|
+
|
|
|
+func NewCacheMap(ctx context.Context, maxSize int, live time.Duration) *CacheMap {
|
|
|
+ cctx, cancel := context.WithCancel(ctx)
|
|
|
+ res := &CacheMap{
|
|
|
+ ctx: cctx,
|
|
|
+ cancel: cancel,
|
|
|
+ items: make(map[any]*item),
|
|
|
+ mi: new(sync.RWMutex),
|
|
|
+ live: live,
|
|
|
+ maxSize: maxSize,
|
|
|
+ mc: new(sync.RWMutex),
|
|
|
+ }
|
|
|
+ // todo - clean worker
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
type CacheMap struct {
|
|
|
- *cacheMap
|
|
|
+ finished bool
|
|
|
+ ctx context.Context
|
|
|
+ cancel context.CancelFunc
|
|
|
+ items map[any]*item
|
|
|
+ mi *sync.RWMutex
|
|
|
+ live time.Duration
|
|
|
+ maxSize int
|
|
|
+ cleanChans []chan map[any]any
|
|
|
+ mc *sync.RWMutex
|
|
|
}
|
|
|
|
|
|
-type cacheMapItem struct {
|
|
|
- value interface{}
|
|
|
- expire int64
|
|
|
+func (s *CacheMap) IsFinished() bool {
|
|
|
+ return s.finished
|
|
|
}
|
|
|
|
|
|
-type cacheMap struct {
|
|
|
- locker *sync.RWMutex
|
|
|
- items map[interface{}]*cacheMapItem
|
|
|
- liveDuration time.Duration
|
|
|
- maxSize int
|
|
|
- cleanerWork bool
|
|
|
- stopCleanerChan chan byte
|
|
|
- cleanedEventChan chan map[interface{}]interface{}
|
|
|
+func (s *CacheMap) close() {
|
|
|
+ s.finished = true
|
|
|
+ s.mc.Lock()
|
|
|
+ for _, ch := range s.cleanChans {
|
|
|
+ close(ch)
|
|
|
+ }
|
|
|
+ s.cleanChans = nil
|
|
|
+ s.mc.Unlock()
|
|
|
+
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) CleanEvent() (eventChan <-chan map[interface{}]interface{}) {
|
|
|
- s.locker.Lock()
|
|
|
- if s.cleanedEventChan == nil {
|
|
|
- s.cleanedEventChan = make(chan map[interface{}]interface{})
|
|
|
+func (s *CacheMap) eventClean(m map[any]any) {
|
|
|
+ s.mc.RLock()
|
|
|
+ for _, ch := range s.cleanChans {
|
|
|
+ select {
|
|
|
+ case ch <- m:
|
|
|
+ default:
|
|
|
+ }
|
|
|
}
|
|
|
- eventChan = s.cleanedEventChan
|
|
|
- s.locker.Unlock()
|
|
|
- return
|
|
|
+ s.mc.RUnlock()
|
|
|
+}
|
|
|
+
|
|
|
+func (s *CacheMap) EventCleaner() <-chan map[any]any {
|
|
|
+ s.mc.Lock()
|
|
|
+ ech := make(chan map[any]any, 1)
|
|
|
+ s.cleanChans = append(s.cleanChans, ech)
|
|
|
+ s.mc.Unlock()
|
|
|
+ return ech
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) runCleaner() {
|
|
|
- ticker := time.NewTicker(s.liveDuration / 2)
|
|
|
+func (s *CacheMap) cleanWorker() {
|
|
|
+ t := time.NewTicker(s.live)
|
|
|
+loop:
|
|
|
for {
|
|
|
select {
|
|
|
- case <-ticker.C:
|
|
|
- {
|
|
|
- now := time.Now().UnixNano()
|
|
|
- s.locker.Lock()
|
|
|
- cleaned := make(map[interface{}]interface{})
|
|
|
- for key, val := range s.items {
|
|
|
- if now > val.expire {
|
|
|
- cleaned[key] = val.value
|
|
|
- delete(s.items, key)
|
|
|
- }
|
|
|
- }
|
|
|
- if len(cleaned) > 0 {
|
|
|
- select {
|
|
|
- case s.cleanedEventChan <- cleaned:
|
|
|
- default:
|
|
|
- }
|
|
|
+ case <-t.C:
|
|
|
+ // clean items
|
|
|
+ nowTS := time.Now().Unix()
|
|
|
+ m := make(map[any]any)
|
|
|
+ s.mi.Lock()
|
|
|
+ for key, item := range s.items {
|
|
|
+ if item.expire < nowTS {
|
|
|
+ delete(s.items, key)
|
|
|
+ m[key] = item.value
|
|
|
}
|
|
|
- if len(s.items) == 0 {
|
|
|
- s.cleanerWork = false
|
|
|
- ticker.Stop()
|
|
|
- s.locker.Unlock()
|
|
|
- return
|
|
|
- }
|
|
|
- s.locker.Unlock()
|
|
|
}
|
|
|
- case <-s.stopCleanerChan:
|
|
|
- {
|
|
|
- s.cleanerWork = false
|
|
|
- ticker.Stop()
|
|
|
- return
|
|
|
+ s.mi.Unlock()
|
|
|
+ if len(m) > 0 {
|
|
|
+ s.eventClean(m)
|
|
|
}
|
|
|
+ case <-s.ctx.Done():
|
|
|
+ break loop
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) delete(key interface{}) {
|
|
|
+func (s *CacheMap) delete(key any) {
|
|
|
delete(s.items, key)
|
|
|
- if len(s.items) == 0 && s.cleanerWork {
|
|
|
- s.stopCleanerChan <- 0
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
// Delete removes cached object
|
|
|
-func (s *cacheMap) Delete(key interface{}) {
|
|
|
- s.locker.Lock()
|
|
|
+func (s *CacheMap) Delete(key any) {
|
|
|
+ s.mi.Lock()
|
|
|
s.delete(key)
|
|
|
- s.locker.Unlock()
|
|
|
+ s.mi.Unlock()
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) set(key, value interface{}) {
|
|
|
- s.items[key] = &cacheMapItem{
|
|
|
+func (s *CacheMap) set(key, value any) {
|
|
|
+ s.items[key] = &item{
|
|
|
value: value,
|
|
|
- expire: time.Now().Add(s.liveDuration).UnixNano(),
|
|
|
+ expire: time.Now().Add(s.live).UnixNano(),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (s *CacheMap) keysOlder(count int) []any {
|
|
|
+ if count > len(s.items) {
|
|
|
+ count = len(s.items)
|
|
|
}
|
|
|
- if !s.cleanerWork {
|
|
|
- s.cleanerWork = true
|
|
|
- go s.runCleaner()
|
|
|
+ last, llast := time.Now().Unix(), int64(-1)
|
|
|
+ res := make([]any, 0, count)
|
|
|
+ for len(res) < count {
|
|
|
+ var rKey any
|
|
|
+ ltmp := last
|
|
|
+ for key, val := range s.items {
|
|
|
+ if val.expire < ltmp && val.expire > llast {
|
|
|
+ rKey = key
|
|
|
+ ltmp = val.expire
|
|
|
+ }
|
|
|
+ }
|
|
|
+ res = append(res, rKey)
|
|
|
+ llast = ltmp
|
|
|
}
|
|
|
+ return res
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) Set(key, value interface{}) {
|
|
|
- s.locker.Lock()
|
|
|
+func (s *CacheMap) Set(key, value any) {
|
|
|
+ s.mi.Lock()
|
|
|
+ if s.maxSize > 0 && len(s.items) == s.maxSize {
|
|
|
+ // remove first
|
|
|
+ for key, val := range s.items {
|
|
|
+ delete(s.items, key)
|
|
|
+ s.eventClean(map[any]any{
|
|
|
+ key: val.value,
|
|
|
+ })
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
s.set(key, value)
|
|
|
- s.locker.Unlock()
|
|
|
+ s.mi.Unlock()
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) SetMulti(m map[interface{}]interface{}) {
|
|
|
- s.locker.Lock()
|
|
|
+func (s *CacheMap) SetMulti(m map[any]any) {
|
|
|
+ s.mi.Lock()
|
|
|
+ /*
|
|
|
+ if s.maxSize > 0 && len(s.items)+len(m) > s.maxSize {
|
|
|
+ // remove first
|
|
|
+ for key, val := range s.items {
|
|
|
+ delete(s.items, key)
|
|
|
+ s.eventClean(map[any]any{
|
|
|
+ key: val.value,
|
|
|
+ })
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ */
|
|
|
for key, val := range m {
|
|
|
s.set(key, val)
|
|
|
}
|
|
|
- s.locker.Unlock()
|
|
|
+ s.mi.Unlock()
|
|
|
}
|
|
|
|
|
|
-func (s *CacheMap) DeleteMulti(keys []interface{}) {
|
|
|
- s.locker.Lock()
|
|
|
+func (s *CacheMap) DeleteMulti(keys []any) {
|
|
|
+ s.mi.Lock()
|
|
|
for _, key := range keys {
|
|
|
delete(s.items, key)
|
|
|
}
|
|
|
- s.locker.Unlock()
|
|
|
+ s.mi.Unlock()
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) get(key interface{}) (res interface{}, check bool) {
|
|
|
- var item *cacheMapItem
|
|
|
- if item, check = s.items[key]; check {
|
|
|
- res = item.value
|
|
|
+func (s *CacheMap) get(key any) (res any, check bool) {
|
|
|
+ var it *item
|
|
|
+ if it, check = s.items[key]; check {
|
|
|
+ res = it.value
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) Get(key interface{}) (res interface{}, check bool) {
|
|
|
- s.locker.RLock()
|
|
|
+func (s *CacheMap) Get(key any) (res any, check bool) {
|
|
|
+ s.mi.RLock()
|
|
|
res, check = s.get(key)
|
|
|
- s.locker.RUnlock()
|
|
|
+ s.mi.RUnlock()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) GetCreate(key interface{}, mCreate CallCreate) (val interface{}, check bool) {
|
|
|
+func (s *CacheMap) GetCreate(key any, mCreate CallCreate) (val any, check bool) {
|
|
|
if val, check = s.Get(key); !check {
|
|
|
- s.locker.Lock()
|
|
|
+ s.mi.Lock()
|
|
|
+ defer s.mi.Unlock()
|
|
|
if val, check = s.get(key); check {
|
|
|
- s.locker.Unlock()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
if val, check = mCreate(key); check {
|
|
|
s.set(key, val)
|
|
|
}
|
|
|
- s.locker.Unlock()
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) GetCreateNew(key interface{}, mCreateNew CallCreateNew) (rKey, val interface{}, check bool) {
|
|
|
+func (s *CacheMap) GetCreateNew(key any, mCreateNew CallCreateNew) (rKey, val any, check bool) {
|
|
|
rKey = key
|
|
|
if val, check = s.Get(key); !check {
|
|
|
- s.locker.Lock()
|
|
|
+ s.mi.Lock()
|
|
|
+ defer s.mi.Unlock()
|
|
|
if val, check = s.get(key); check {
|
|
|
- s.locker.Unlock()
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
if rKey, val, check = mCreateNew(key); check {
|
|
|
s.set(rKey, val)
|
|
|
}
|
|
|
- s.locker.Unlock()
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) GetCheck(key interface{}, mCheck CallCheck) (res interface{}, check bool) {
|
|
|
- s.locker.Lock()
|
|
|
+func (s *CacheMap) GetCheck(key any, mCheck CallCheck) (res any, check bool) {
|
|
|
+ s.mi.Lock()
|
|
|
res, check = s.get(key)
|
|
|
if rKey, rVal, rCheck := mCheck(key, res, check); rCheck {
|
|
|
s.set(rKey, rVal)
|
|
|
key, res, check = rKey, rVal, true
|
|
|
}
|
|
|
- s.locker.Unlock()
|
|
|
+ s.mi.Unlock()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// Each implements a map bypass for each key using the callback function. If the callback function returns false, then the cycle stops
|
|
|
-func (s *cacheMap) Each(callback func(interface{}, interface{}) bool) {
|
|
|
- s.locker.RLock()
|
|
|
+func (s *CacheMap) Each(callback func(any, any) bool) {
|
|
|
+ s.mi.RLock()
|
|
|
+ defer s.mi.RUnlock()
|
|
|
for key, val := range s.items {
|
|
|
if !callback(key, val.value) {
|
|
|
- s.locker.RUnlock()
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
- s.locker.RUnlock()
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) Len() (res int) {
|
|
|
- s.locker.RLock()
|
|
|
+func (s CacheMap) Len() (res int) {
|
|
|
+ s.mi.RLock()
|
|
|
res = len(s.items)
|
|
|
- s.locker.RUnlock()
|
|
|
+ s.mi.RUnlock()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) Keys() (res []interface{}) {
|
|
|
- s.locker.RLock()
|
|
|
- res = make([]interface{}, 0, len(s.items))
|
|
|
+func (s *CacheMap) Keys() (res []any) {
|
|
|
+ s.mi.RLock()
|
|
|
+ res = make([]any, 0, len(s.items))
|
|
|
for key := range s.items {
|
|
|
res = append(res, key)
|
|
|
}
|
|
|
- s.locker.RUnlock()
|
|
|
+ s.mi.RUnlock()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
-func (s *cacheMap) Clear() {
|
|
|
- s.locker.Lock()
|
|
|
- s.items = make(map[interface{}]*cacheMapItem)
|
|
|
- s.locker.Unlock()
|
|
|
-}
|
|
|
-
|
|
|
-// for garbage collector
|
|
|
-func destroyCacheMap(m *CacheMap) {
|
|
|
- close(m.stopCleanerChan)
|
|
|
- if m.cleanedEventChan != nil {
|
|
|
- close(m.cleanedEventChan)
|
|
|
- }
|
|
|
+func (s *CacheMap) Clear() {
|
|
|
+ s.mi.Lock()
|
|
|
+ s.items = make(map[any]*item)
|
|
|
+ s.mi.Unlock()
|
|
|
}
|