mirror of
https://github.com/PrivateCaptcha/PrivateCaptcha.git
synced 2026-05-13 00:08:34 -05:00
1dc330150c
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)
119 lines
2.5 KiB
Go
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
|
|
}
|