| package cache |
|
|
| import ( |
| "container/list" |
| "context" |
| "sync" |
| "time" |
|
|
| "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| ) |
|
|
| type lruEntry struct { |
| key string |
| response *CachedResponse |
| expiresAt time.Time |
| } |
|
|
| type memoryResponseCache struct { |
| cfg config.ResponseCacheConfig |
| mu sync.RWMutex |
| items map[string]*list.Element |
| eviction *list.List |
| stats responseCacheStats |
| } |
|
|
| func newMemoryResponseCache(cfg config.ResponseCacheConfig) *memoryResponseCache { |
| maxEntries := cfg.MaxMemoryEntries |
| if maxEntries <= 0 { |
| maxEntries = 1000 |
| } |
| return &memoryResponseCache{ |
| cfg: cfg, |
| items: make(map[string]*list.Element, maxEntries), |
| eviction: list.New(), |
| } |
| } |
|
|
| func (m *memoryResponseCache) Get(_ context.Context, key string) (*CachedResponse, bool) { |
| m.mu.RLock() |
| elem, ok := m.items[key] |
| m.mu.RUnlock() |
| if !ok { |
| m.stats.recordMiss() |
| return nil, false |
| } |
|
|
| entry := elem.Value.(*lruEntry) |
| if time.Now().After(entry.expiresAt) { |
| |
| m.mu.Lock() |
| m.removeElement(elem) |
| m.mu.Unlock() |
| m.stats.recordMiss() |
| return nil, false |
| } |
|
|
| |
| m.mu.Lock() |
| m.eviction.MoveToFront(elem) |
| m.mu.Unlock() |
|
|
| m.stats.recordHit() |
| return entry.response, true |
| } |
|
|
| func (m *memoryResponseCache) Set(_ context.Context, key string, resp *CachedResponse, ttl time.Duration) error { |
| m.mu.Lock() |
| defer m.mu.Unlock() |
|
|
| |
| if elem, ok := m.items[key]; ok { |
| entry := elem.Value.(*lruEntry) |
| entry.response = resp |
| entry.expiresAt = time.Now().Add(ttl) |
| m.eviction.MoveToFront(elem) |
| return nil |
| } |
|
|
| maxEntries := m.cfg.MaxMemoryEntries |
| if maxEntries <= 0 { |
| maxEntries = 1000 |
| } |
|
|
| |
| for len(m.items) >= maxEntries && m.eviction.Len() > 0 { |
| back := m.eviction.Back() |
| if back != nil { |
| m.removeElement(back) |
| } |
| } |
|
|
| |
| entry := &lruEntry{ |
| key: key, |
| response: resp, |
| expiresAt: time.Now().Add(ttl), |
| } |
| elem := m.eviction.PushFront(entry) |
| m.items[key] = elem |
| return nil |
| } |
|
|
| func (m *memoryResponseCache) Delete(_ context.Context, _ string) error { |
| m.mu.Lock() |
| defer m.mu.Unlock() |
| m.items = make(map[string]*list.Element) |
| m.eviction.Init() |
| return nil |
| } |
|
|
| func (m *memoryResponseCache) Stats() CacheStats { |
| m.mu.RLock() |
| size := int64(len(m.items)) |
| m.mu.RUnlock() |
| return CacheStats{ |
| Hits: m.stats.hits.Load(), |
| Misses: m.stats.misses.Load(), |
| Size: size, |
| } |
| } |
|
|
| func (m *memoryResponseCache) Close() error { |
| return nil |
| } |
|
|
| func (m *memoryResponseCache) removeElement(elem *list.Element) { |
| entry := elem.Value.(*lruEntry) |
| delete(m.items, entry.key) |
| m.eviction.Remove(elem) |
| } |
|
|