Files
PrivateCaptcha/pkg/db/staticcache.go
T
Taras Kushnir 1dc330150c Reread cached properties from time to time
There was a problem where, after upgrading otter to v2, there was a chance
of properties being just cached and not reread. The reason we couldn't use
otter's magic reloading is that there's more business logic that needs to
be executed than just to reread value from DB.

One drawback is in order to keep otter's package "hidden", a clone of Loader
had to be created in common.

Additionally Verify API of server was refactored so we return a clear error
externally in case of auth failure (related to "delayed" API key check)
2025-06-21 14:16:28 +03:00

119 lines
2.5 KiB
Go

package db
import (
"context"
"log/slog"
"sync"
"time"
"github.com/PrivateCaptcha/PrivateCaptcha/pkg/common"
)
type StaticCache[TKey comparable, TValue comparable] struct {
cache map[TKey]TValue
mux sync.RWMutex
upperBound int
lowerBound int
missingValue TValue
}
var _ common.Cache[int, any] = (*StaticCache[int, any])(nil)
func NewStaticCache[TKey comparable, TValue comparable](capacity int, missingValue TValue) *StaticCache[TKey, TValue] {
return &StaticCache[TKey, TValue]{
cache: make(map[TKey]TValue),
upperBound: capacity,
lowerBound: capacity/2 + capacity/4,
missingValue: missingValue,
}
}
func (c *StaticCache[TKey, TValue]) HitRatio() float64 {
// unsupported
return 0.0
}
func (c *StaticCache[TKey, TValue]) Missing() TValue {
return c.missingValue
}
func (c *StaticCache[TKey, TValue]) Get(ctx context.Context, key TKey) (TValue, error) {
c.mux.RLock()
defer c.mux.RUnlock()
if item, ok := c.cache[key]; ok {
if item == c.missingValue {
return c.missingValue, ErrNegativeCacheHit
}
return item, nil
} else {
return c.missingValue, ErrCacheMiss
}
}
func (c *StaticCache[TKey, TValue]) GetEx(ctx context.Context, key TKey, loader common.CacheLoader[TKey, TValue]) (TValue, error) {
c.mux.Lock()
defer c.mux.Unlock()
var err error
item, ok := c.cache[key]
if !ok {
if item, err = loader.Load(ctx, key); err == nil {
c.cache[key] = item
} else {
slog.ErrorContext(ctx, "Failed to load the value", "key", key, common.ErrAttr(err))
return c.missingValue, ErrCacheMiss
}
}
if item == c.missingValue {
return c.missingValue, ErrNegativeCacheHit
}
return item, nil
}
func (c *StaticCache[TKey, TValue]) SetMissing(ctx context.Context, key TKey) error {
c.mux.Lock()
defer c.mux.Unlock()
c.cache[key] = c.missingValue
return nil
}
func (c *StaticCache[TKey, TValue]) compressUnsafe() {
for k := range c.cache {
if len(c.cache) <= c.lowerBound {
break
}
delete(c.cache, k)
}
}
func (c *StaticCache[TKey, TValue]) Set(ctx context.Context, key TKey, t TValue) error {
c.mux.Lock()
defer c.mux.Unlock()
if len(c.cache) >= c.upperBound {
c.compressUnsafe()
}
c.cache[key] = t
return nil
}
func (c *StaticCache[TKey, TValue]) SetWithTTL(ctx context.Context, key TKey, t TValue, _ time.Duration) error {
// ttl is not supported here
return c.Set(ctx, key, t)
}
func (c *StaticCache[TKey, TValue]) Delete(ctx context.Context, key TKey) error {
c.mux.Lock()
defer c.mux.Unlock()
delete(c.cache, key)
return nil
}