Files
openserp/core/cache.go
2026-04-01 17:38:13 +03:00

141 lines
2.5 KiB
Go

package core
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"sync"
"time"
)
type CacheEntry struct {
Data []byte
CreatedAt time.Time
ExpiresAt time.Time
}
// ResponseCache is a bounded in-memory TTL cache for dedicated endpoint responses.
type ResponseCache struct {
mu sync.Mutex
entries map[string]CacheEntry
ttl time.Duration
maxSize int
hits int
misses int
bypasses int
evictions int
}
func NewResponseCache(ttl time.Duration, maxSize int) *ResponseCache {
return &ResponseCache{
entries: make(map[string]CacheEntry),
ttl: ttl,
maxSize: maxSize,
}
}
func BuildCacheKey(engine string, action string, q Query) string {
raw := fmt.Sprintf(
"%s|%s|%s|%s|%s|%s|%s|%d|%d|%t|%t|%s",
engine,
action,
q.Text,
q.LangCode,
q.DateInterval,
q.Filetype,
q.Site,
q.Limit,
q.Start,
q.Filter,
q.Answers,
q.ProxyOverride,
)
hash := sha256.Sum256([]byte(raw))
return hex.EncodeToString(hash[:])
}
func (c *ResponseCache) Get(key string) ([]byte, bool) {
c.mu.Lock()
defer c.mu.Unlock()
c.pruneExpiredLocked(time.Now())
entry, ok := c.entries[key]
if !ok {
c.misses++
return nil, false
}
c.hits++
return entry.Data, true
}
func (c *ResponseCache) Set(key string, data []byte) {
c.mu.Lock()
defer c.mu.Unlock()
now := time.Now()
c.pruneExpiredLocked(now)
if _, exists := c.entries[key]; !exists && len(c.entries) >= c.maxSize {
c.evictOldestLocked()
}
c.entries[key] = CacheEntry{
Data: data,
CreatedAt: now,
ExpiresAt: now.Add(c.ttl),
}
}
func (c *ResponseCache) RecordBypass() {
c.mu.Lock()
defer c.mu.Unlock()
c.bypasses++
}
func (c *ResponseCache) Stats() map[string]interface{} {
c.mu.Lock()
defer c.mu.Unlock()
c.pruneExpiredLocked(time.Now())
return map[string]interface{}{
"status": true,
"entries": len(c.entries),
"hits": c.hits,
"misses": c.misses,
"bypasses": c.bypasses,
"evictions": c.evictions,
"ttl_seconds": int(c.ttl / time.Second),
"max_size": c.maxSize,
}
}
func (c *ResponseCache) pruneExpiredLocked(now time.Time) {
for key, entry := range c.entries {
if !now.Before(entry.ExpiresAt) {
delete(c.entries, key)
}
}
}
func (c *ResponseCache) evictOldestLocked() {
var (
oldestKey string
oldestCreated time.Time
)
for key, entry := range c.entries {
if oldestKey == "" || entry.CreatedAt.Before(oldestCreated) {
oldestKey = key
oldestCreated = entry.CreatedAt
}
}
if oldestKey != "" {
delete(c.entries, oldestKey)
c.evictions++
}
}