0
Fork 0
mirror of https://github.com/willnorris/imageproxy.git synced 2025-01-20 22:53:00 -05:00
imageproxy/vendor/github.com/die-net/lrucache/lrucache.go
2017-09-17 11:20:04 +00:00

146 lines
3.1 KiB
Go

// Package lrucache provides a byte-size-limited implementation of
// httpcache.Cache that stores data in memory.
package lrucache
import (
"container/list"
"sync"
"time"
)
// LruCache is a thread-safe, in-memory httpcache.Cache that evicts the
// least recently used entries from memory when either MaxSize (in bytes)
// limit would be exceeded or (if set) the entries are older than MaxAge (in
// seconds). Use the New constructor to create one.
type LruCache struct {
MaxSize int64
MaxAge int64
mu sync.Mutex
cache map[string]*list.Element
lru *list.List // Front is least-recent
size int64
}
// New creates an LruCache that will restrict itself to maxSize bytes of
// memory. If maxAge > 0, entries will also be expired after maxAge
// seconds.
func New(maxSize int64, maxAge int64) *LruCache {
c := &LruCache{
MaxSize: maxSize,
MaxAge: maxAge,
lru: list.New(),
cache: make(map[string]*list.Element),
}
return c
}
// Get returns the []byte representation of a cached response and a bool
// set to true if the key was found.
func (c *LruCache) Get(key string) ([]byte, bool) {
c.mu.Lock()
le, ok := c.cache[key]
if !ok {
c.mu.Unlock() // Avoiding defer overhead
return nil, false
}
if c.MaxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
c.deleteElement(le)
c.maybeDeleteOldest()
c.mu.Unlock() // Avoiding defer overhead
return nil, false
}
c.lru.MoveToBack(le)
value := le.Value.(*entry).value
c.mu.Unlock() // Avoiding defer overhead
return value, true
}
// Set stores the []byte representation of a response for a given key.
func (c *LruCache) Set(key string, value []byte) {
c.mu.Lock()
expires := int64(0)
if c.MaxAge > 0 {
expires = time.Now().Unix() + c.MaxAge
}
if le, ok := c.cache[key]; ok {
c.lru.MoveToBack(le)
e := le.Value.(*entry)
c.size += int64(len(value)) - int64(len(e.value))
e.value = value
e.expires = expires
} else {
e := &entry{key: key, value: value, expires: expires}
c.cache[key] = c.lru.PushBack(e)
c.size += e.size()
}
c.maybeDeleteOldest()
c.mu.Unlock()
}
// Delete removes the value associated with a key.
func (c *LruCache) Delete(key string) {
c.mu.Lock()
if le, ok := c.cache[key]; ok {
c.deleteElement(le)
}
c.mu.Unlock()
}
// Size returns the estimated current memory usage of LruCache.
func (c *LruCache) Size() int64 {
c.mu.Lock()
size := c.size
c.mu.Unlock()
return size
}
func (c *LruCache) maybeDeleteOldest() {
for c.size > c.MaxSize {
le := c.lru.Front()
if le == nil {
panic("LruCache: non-zero size but empty lru")
}
c.deleteElement(le)
}
if c.MaxAge > 0 {
now := time.Now().Unix()
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
c.deleteElement(le)
}
}
}
func (c *LruCache) deleteElement(le *list.Element) {
c.lru.Remove(le)
e := le.Value.(*entry)
delete(c.cache, e.key)
c.size -= e.size()
}
// Rough estimate of map + entry object + string + byte slice overheads in bytes.
const entryOverhead = 168
type entry struct {
key string
value []byte
expires int64
}
func (e *entry) size() int64 {
return entryOverhead + int64(len(e.key)) + int64(len(e.value))
}