243 lines
5.6 KiB
Go
243 lines
5.6 KiB
Go
package cache
|
|
|
|
import (
|
|
"container/list"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// LRUIndex maintains an in-memory LRU index of cache entries
|
|
type LRUIndex struct {
|
|
mu sync.RWMutex
|
|
entries map[string]*list.Element // path -> list element
|
|
lruList *list.List // LRU order (front = most recent)
|
|
maxSize int64 // Maximum total size in bytes
|
|
currSize int64 // Current total size in bytes
|
|
|
|
// Stats
|
|
hitCount int64
|
|
missCount int64
|
|
evictionCount int64
|
|
}
|
|
|
|
// lruEntry wraps CacheEntry for the linked list
|
|
type lruEntry struct {
|
|
entry *CacheEntry
|
|
}
|
|
|
|
// NewLRUIndex creates a new LRU index
|
|
func NewLRUIndex(maxSizeGB int) *LRUIndex {
|
|
return &LRUIndex{
|
|
entries: make(map[string]*list.Element),
|
|
lruList: list.New(),
|
|
maxSize: int64(maxSizeGB) * 1024 * 1024 * 1024, // GB to bytes
|
|
}
|
|
}
|
|
|
|
// Get retrieves a cache entry and updates its LRU position
|
|
// Returns nil if not found
|
|
func (idx *LRUIndex) Get(path string) *CacheEntry {
|
|
idx.mu.Lock()
|
|
defer idx.mu.Unlock()
|
|
|
|
elem, exists := idx.entries[path]
|
|
if !exists {
|
|
idx.missCount++
|
|
return nil
|
|
}
|
|
|
|
// Move to front (most recently used)
|
|
idx.lruList.MoveToFront(elem)
|
|
|
|
// Update last accessed time
|
|
entry := elem.Value.(*lruEntry).entry
|
|
entry.LastAccessed = time.Now()
|
|
|
|
idx.hitCount++
|
|
return entry
|
|
}
|
|
|
|
// Put adds or updates a cache entry
|
|
func (idx *LRUIndex) Put(entry *CacheEntry) {
|
|
idx.mu.Lock()
|
|
defer idx.mu.Unlock()
|
|
|
|
if elem, exists := idx.entries[entry.OriginalPath]; exists {
|
|
// Update existing entry
|
|
oldEntry := elem.Value.(*lruEntry).entry
|
|
idx.currSize -= oldEntry.Size
|
|
idx.currSize += entry.Size
|
|
|
|
elem.Value.(*lruEntry).entry = entry
|
|
idx.lruList.MoveToFront(elem)
|
|
} else {
|
|
// Add new entry
|
|
le := &lruEntry{entry: entry}
|
|
elem := idx.lruList.PushFront(le)
|
|
idx.entries[entry.OriginalPath] = elem
|
|
idx.currSize += entry.Size
|
|
}
|
|
}
|
|
|
|
// Remove removes a cache entry by path
|
|
// Returns the removed entry or nil if not found
|
|
func (idx *LRUIndex) Remove(path string) *CacheEntry {
|
|
idx.mu.Lock()
|
|
defer idx.mu.Unlock()
|
|
|
|
elem, exists := idx.entries[path]
|
|
if !exists {
|
|
return nil
|
|
}
|
|
|
|
entry := elem.Value.(*lruEntry).entry
|
|
idx.lruList.Remove(elem)
|
|
delete(idx.entries, path)
|
|
idx.currSize -= entry.Size
|
|
|
|
return entry
|
|
}
|
|
|
|
// GetLRUEntry returns the least recently used entry without removing it
|
|
// Returns nil if the index is empty
|
|
func (idx *LRUIndex) GetLRUEntry() *CacheEntry {
|
|
idx.mu.RLock()
|
|
defer idx.mu.RUnlock()
|
|
|
|
back := idx.lruList.Back()
|
|
if back == nil {
|
|
return nil
|
|
}
|
|
|
|
return back.Value.(*lruEntry).entry
|
|
}
|
|
|
|
// PopLRUEntry removes and returns the least recently used entry
|
|
// Returns nil if the index is empty
|
|
func (idx *LRUIndex) PopLRUEntry() *CacheEntry {
|
|
idx.mu.Lock()
|
|
defer idx.mu.Unlock()
|
|
|
|
back := idx.lruList.Back()
|
|
if back == nil {
|
|
return nil
|
|
}
|
|
|
|
entry := back.Value.(*lruEntry).entry
|
|
idx.lruList.Remove(back)
|
|
delete(idx.entries, entry.OriginalPath)
|
|
idx.currSize -= entry.Size
|
|
idx.evictionCount++
|
|
|
|
return entry
|
|
}
|
|
|
|
// GetExpiredEntries returns all entries that have exceeded the TTL
|
|
func (idx *LRUIndex) GetExpiredEntries(ttl time.Duration) []*CacheEntry {
|
|
idx.mu.RLock()
|
|
defer idx.mu.RUnlock()
|
|
|
|
var expired []*CacheEntry
|
|
cutoff := time.Now().Add(-ttl)
|
|
|
|
for elem := idx.lruList.Back(); elem != nil; elem = elem.Prev() {
|
|
entry := elem.Value.(*lruEntry).entry
|
|
if entry.CachedAt.Before(cutoff) {
|
|
expired = append(expired, entry)
|
|
}
|
|
}
|
|
|
|
return expired
|
|
}
|
|
|
|
// Size returns the current total size of cached files
|
|
func (idx *LRUIndex) Size() int64 {
|
|
idx.mu.RLock()
|
|
defer idx.mu.RUnlock()
|
|
return idx.currSize
|
|
}
|
|
|
|
// Count returns the number of entries in the index
|
|
func (idx *LRUIndex) Count() int {
|
|
idx.mu.RLock()
|
|
defer idx.mu.RUnlock()
|
|
return len(idx.entries)
|
|
}
|
|
|
|
// MaxSize returns the maximum cache size
|
|
func (idx *LRUIndex) MaxSize() int64 {
|
|
return idx.maxSize
|
|
}
|
|
|
|
// NeedsEviction returns true if the cache size exceeds the maximum
|
|
func (idx *LRUIndex) NeedsEviction() bool {
|
|
idx.mu.RLock()
|
|
defer idx.mu.RUnlock()
|
|
return idx.currSize > idx.maxSize
|
|
}
|
|
|
|
// Stats returns cache statistics
|
|
func (idx *LRUIndex) Stats() *CacheStats {
|
|
idx.mu.RLock()
|
|
defer idx.mu.RUnlock()
|
|
|
|
total := idx.hitCount + idx.missCount
|
|
var hitRate float64
|
|
if total > 0 {
|
|
hitRate = float64(idx.hitCount) / float64(total) * 100
|
|
}
|
|
|
|
return &CacheStats{
|
|
Enabled: true,
|
|
TotalSize: idx.currSize,
|
|
MaxSize: idx.maxSize,
|
|
EntryCount: len(idx.entries),
|
|
HitCount: idx.hitCount,
|
|
MissCount: idx.missCount,
|
|
EvictionCount: idx.evictionCount,
|
|
HitRate: hitRate,
|
|
}
|
|
}
|
|
|
|
// GetAllEntries returns all cache entries (for persistence)
|
|
func (idx *LRUIndex) GetAllEntries() map[string]*CacheEntry {
|
|
idx.mu.RLock()
|
|
defer idx.mu.RUnlock()
|
|
|
|
result := make(map[string]*CacheEntry, len(idx.entries))
|
|
for path, elem := range idx.entries {
|
|
result[path] = elem.Value.(*lruEntry).entry
|
|
}
|
|
return result
|
|
}
|
|
|
|
// LoadFromMap loads entries from a map (for loading persisted index)
|
|
func (idx *LRUIndex) LoadFromMap(entries map[string]*CacheEntry) {
|
|
idx.mu.Lock()
|
|
defer idx.mu.Unlock()
|
|
|
|
// Clear existing entries
|
|
idx.lruList.Init()
|
|
idx.entries = make(map[string]*list.Element)
|
|
idx.currSize = 0
|
|
|
|
// Load entries sorted by LastAccessed (oldest first for proper LRU order)
|
|
// For simplicity, we just add them in arbitrary order - the access pattern will sort them
|
|
for _, entry := range entries {
|
|
le := &lruEntry{entry: entry}
|
|
elem := idx.lruList.PushBack(le) // Add to back (oldest)
|
|
idx.entries[entry.OriginalPath] = elem
|
|
idx.currSize += entry.Size
|
|
}
|
|
}
|
|
|
|
// Clear removes all entries from the index
|
|
func (idx *LRUIndex) Clear() {
|
|
idx.mu.Lock()
|
|
defer idx.mu.Unlock()
|
|
|
|
idx.lruList.Init()
|
|
idx.entries = make(map[string]*list.Element)
|
|
idx.currSize = 0
|
|
}
|