package cache import ( "context" "sync" "time" ) 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 { 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 } func (s *CacheMap) IsFinished() bool { return s.finished } 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) eventClean(m map[any]any) { s.mc.RLock() for _, ch := range s.cleanChans { select { case ch <- m: default: } } 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) cleanWorker() { defer func() { for _, v := range s.cleanChans { close(v) } }() t := time.NewTicker(s.live) loop: for { select { 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 } } s.mi.Unlock() if len(m) > 0 { s.eventClean(m) } case <-s.ctx.Done(): break loop } } } func (s *CacheMap) delete(key any) { delete(s.items, key) } // Delete removes cached object func (s *CacheMap) Delete(key any) { s.mi.Lock() s.delete(key) s.mi.Unlock() } func (s *CacheMap) set(key, value any) { s.items[key] = &item{ value: value, expire: time.Now().Add(s.live).UnixNano(), } } func (s *CacheMap) keysOlder(count int) []any { if count > len(s.items) { count = len(s.items) } 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 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.mi.Unlock() } 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.mi.Unlock() } func (s *CacheMap) DeleteMulti(keys []any) { s.mi.Lock() for _, key := range keys { delete(s.items, key) } s.mi.Unlock() } 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 any) (res any, check bool) { s.mi.RLock() res, check = s.get(key) s.mi.RUnlock() return } func (s *CacheMap) GetCreate(key any, mCreate CallCreate) (val any, check bool) { if val, check = s.Get(key); !check { s.mi.Lock() defer s.mi.Unlock() if val, check = s.get(key); check { return } if val, check = mCreate(key); check { s.set(key, val) } } return } func (s *CacheMap) GetCreateNew(key any, mCreateNew CallCreateNew) (rKey, val any, check bool) { rKey = key if val, check = s.Get(key); !check { s.mi.Lock() defer s.mi.Unlock() if val, check = s.get(key); check { return } if rKey, val, check = mCreateNew(key); check { s.set(rKey, val) } } return } 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.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(any, any) bool) { s.mi.RLock() defer s.mi.RUnlock() for key, val := range s.items { if !callback(key, val.value) { return } } } func (s CacheMap) Len() (res int) { s.mi.RLock() res = len(s.items) s.mi.RUnlock() return } 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.mi.RUnlock() return } func (s *CacheMap) Clear() { s.mi.Lock() s.items = make(map[any]*item) s.mi.Unlock() }