more work on image cache

This commit is contained in:
Drew Weymouth
2023-02-02 11:42:51 -08:00
parent ab5612eba4
commit 0c06e13423
+54 -12
View File
@@ -18,10 +18,11 @@ type CacheItem struct {
} }
// A custom cache for images with the following eviction strategy: // A custom cache for images with the following eviction strategy:
// - If there are fewer than MinSize items in the cache, none will be evicted // 1. If there are fewer than MinSize items in the cache, none will be evicted
// - If a new addition would make the cache exceed MaxSize, an item will be immediately evicted // 2. If a new addition would make the cache exceed MaxSize, an item will be immediately evicted
// - evicted item will be an expired item if possible, else the least recently used // 2a. in this case, evict the LRU expired item or if none expired, the LRU item
// - If the size of the cache is between MaxSize and MinSize, expired items will be periodically evicted // 3. If the size of the cache is between MaxSize and MinSize, expired items will be periodically evicted
// 3a. in this case, again the least recently used expired items will be evicted first
type ImageCache struct { type ImageCache struct {
MinSize int MinSize int
MaxSize int MaxSize int
@@ -39,6 +40,7 @@ func (i *ImageCache) Init(ctx context.Context) {
i.cache = make(map[string]CacheItem) i.cache = make(map[string]CacheItem)
} }
// holds writer lock for O(i.MaxSize) worst case
func (i *ImageCache) AddWithTTL(key string, val image.Image, ttl time.Duration) { func (i *ImageCache) AddWithTTL(key string, val image.Image, ttl time.Duration) {
i.mu.Lock() i.mu.Lock()
defer i.mu.Unlock() defer i.mu.Unlock()
@@ -87,28 +89,61 @@ func (i *ImageCache) Get(key string, resetTTL bool) (image.Image, error) {
return nil, ErrNotFound return nil, ErrNotFound
} }
// assuming max size is small enough that linear scan is fine func (i *ImageCache) GetWithNewTTL(key string, newTtl time.Duration) (image.Image, error) {
i.mu.RLock()
defer i.mu.RUnlock()
if v, ok := i.cache[key]; ok {
v.lastAccessed = time.Now().Unix()
v.expiresAt = time.Now().Add(newTtl).Unix()
v.ttl = newTtl
return v.val, nil
}
return nil, ErrNotFound
}
// must be called when rwmutex is already acquired for writing
func (i *ImageCache) evictOne() { func (i *ImageCache) evictOne() {
var lruKey string
now := time.Now().Unix() now := time.Now().Unix()
var lruKey string
lruTime := now lruTime := now
var lruExpiredKey string
lruExpiredTime := now
for k, v := range i.cache { for k, v := range i.cache {
if v.expiresAt < now { if v.expiresAt < now && v.lastAccessed < lruExpiredTime {
delete(i.cache, k) lruExpiredTime = v.lastAccessed
return lruExpiredKey = k
} }
if v.lastAccessed < lruTime { if v.lastAccessed < lruTime {
lruTime = v.lastAccessed lruTime = v.lastAccessed
lruKey = k lruKey = k
} }
} }
delete(i.cache, lruKey) if lruExpiredTime < now {
// deleting LRU expired item
delete(i.cache, lruExpiredKey)
} else {
// no expired items, delete LRU non-expired item
delete(i.cache, lruKey)
}
}
type expiredItem struct {
key string
lastAccessed int64
} }
// TODO // TODO
func (i *ImageCache) EvictExpired() { func (i *ImageCache) EvictExpired() {
i.mu.Lock() i.mu.RLock()
defer i.mu.Unlock() expired := make([]expiredItem, 0, len(i.cache)-i.MinSize)
now := time.Now().Unix()
for k, v := range i.cache {
if v.expiresAt < now {
expired = append(expired, expiredItem{key: k, lastAccessed: v.lastAccessed})
}
}
i.mu.RUnlock()
count := len(i.cache) count := len(i.cache)
for count > i.MinSize { for count > i.MinSize {
@@ -116,3 +151,10 @@ func (i *ImageCache) EvictExpired() {
} }
} }
func heapify(arr *[]expiredItem, i int) {
//smallest := i
//lChild := 2*i + 1
//rChild := 2*i + 2
}