143 lines
2.9 KiB
Go
143 lines
2.9 KiB
Go
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
|
|
}
|