package dataset import ( "fmt" "net" "net/netip" "sync" "time" "git.maze.io/maze/styx/logger" ) const MinCacheExpire = 10 * time.Second type cache struct { Storage expire time.Duration groupByID sync.Map clientByAddr sync.Map listByGroup sync.Map closed chan struct{} } type cacheItem struct { cachedAt time.Time value any } // Cache items returned from a Storage for the specified duration. // // Does not cache negative hits. func Cache(storage Storage, expire time.Duration) Storage { if expire < MinCacheExpire { expire = MinCacheExpire } logger.StandardLog.Value("expire", expire).Debug("Caching Storage responses") s := &cache{ Storage: storage, expire: expire, closed: make(chan struct{}, 1), } go s.cleanUpTimer() return s } func (s *cache) cleanUpTimer() { ticker := time.NewTicker(s.expire) defer ticker.Stop() for { select { case <-s.closed: return case now := <-ticker.C: logger.StandardLog.Trace("Cache cleanup running") s.cleanUp(now, &s.groupByID) s.cleanUp(now, &s.clientByAddr) s.cleanUp(now, &s.listByGroup) } } } func (s *cache) cleanUp(now time.Time, cacheMap *sync.Map) { cacheMap.Range(func(key, item any) bool { cached := item.(cacheItem) if ago := now.Sub(cached.cachedAt); ago >= s.expire { logger.StandardLog.Values(logger.Values{ "ago": ago, "type": fmt.Sprintf("%T", cached.value), "item": fmt.Sprintf("%s", cached.value), }).Debug("Cache removing expired item") cacheMap.Delete(key) } return true }) } func (s *cache) load(now time.Time, cacheMap *sync.Map, key any) (value any, ok bool) { var item any if item, ok = cacheMap.Load(key); !ok { return } cached := item.(cacheItem) if now.Sub(cached.cachedAt) < s.expire { return cached.value, true } return nil, false } func (s *cache) save(now time.Time, cacheMap *sync.Map, key, value any) { cacheMap.Store(key, cacheItem{ cachedAt: now, value: value, }) } func (s *cache) GroupByID(id int64) (Group, error) { now := time.Now() if value, ok := s.load(now, &s.groupByID, id); ok { return value.(Group), nil } group, err := s.Storage.GroupByID(id) if err == nil { s.save(now, &s.groupByID, id, group) } return group, err } func (s *cache) ClientByIP(ip net.IP) (Client, error) { addr, _ := netip.AddrFromSlice(ip) return s.ClientByAddr(addr) } func (s *cache) ClientByAddr(ip netip.Addr) (Client, error) { now := time.Now() if value, ok := s.load(now, &s.clientByAddr, ip); ok { return value.(Client), nil } client, err := s.Storage.ClientByAddr(ip) if err == nil { s.save(now, &s.clientByAddr, ip, client) } return client, err } func (s *cache) ListsByGroup(group Group) ([]List, error) { now := time.Now() if value, ok := s.load(now, &s.listByGroup, group.ID); ok { return value.([]List), nil } lists, err := s.Storage.ListsByGroup(group) if err == nil { s.save(now, &s.listByGroup, group.ID, lists) } return lists, err }