package httpapi import ( "container/list" "sync" ) const ( defaultRawCacheMaxEntries = 32 defaultRawCacheMaxBytes = 64 * 1024 * 1024 ) type rawContentCache struct { mu sync.Mutex maxEntries int maxBytes int64 totalBytes int64 items map[string]*list.Element lru *list.List inFlight map[string]*rawContentLoad } type rawContentItem struct { id string data []byte size int64 } type rawContentLoad struct { wait chan struct{} data []byte err error } func newRawContentCache(maxEntries int, maxBytes int64) *rawContentCache { if maxEntries <= 0 { maxEntries = defaultRawCacheMaxEntries } if maxBytes <= 0 { maxBytes = defaultRawCacheMaxBytes } return &rawContentCache{ maxEntries: maxEntries, maxBytes: maxBytes, items: make(map[string]*list.Element, maxEntries), lru: list.New(), inFlight: make(map[string]*rawContentLoad), } } func (c *rawContentCache) GetOrLoad(id string, loader func() ([]byte, error)) ([]byte, error, bool) { c.mu.Lock() if elem, ok := c.items[id]; ok { c.lru.MoveToFront(elem) item := elem.Value.(*rawContentItem) data := item.data c.mu.Unlock() return data, nil, true } if load, ok := c.inFlight[id]; ok { c.mu.Unlock() <-load.wait return load.data, load.err, false } load := &rawContentLoad{wait: make(chan struct{})} c.inFlight[id] = load c.mu.Unlock() data, err := loader() load.data = data load.err = err c.mu.Lock() delete(c.inFlight, id) if err == nil { c.setLocked(id, data) } c.mu.Unlock() close(load.wait) return data, err, false } func (c *rawContentCache) Delete(id string) { c.mu.Lock() defer c.mu.Unlock() c.deleteLocked(id) } func (c *rawContentCache) setLocked(id string, data []byte) { size := int64(len(data)) if size <= 0 || size > c.maxBytes { c.deleteLocked(id) return } if elem, ok := c.items[id]; ok { item := elem.Value.(*rawContentItem) c.totalBytes -= item.size item.data = data item.size = size c.totalBytes += size c.lru.MoveToFront(elem) c.evictLocked() return } item := &rawContentItem{ id: id, data: data, size: size, } elem := c.lru.PushFront(item) c.items[id] = elem c.totalBytes += size c.evictLocked() } func (c *rawContentCache) deleteLocked(id string) { elem, ok := c.items[id] if !ok { return } item := elem.Value.(*rawContentItem) c.totalBytes -= item.size delete(c.items, id) c.lru.Remove(elem) } func (c *rawContentCache) evictLocked() { for c.totalBytes > c.maxBytes || len(c.items) > c.maxEntries { last := c.lru.Back() if last == nil { return } item := last.Value.(*rawContentItem) c.totalBytes -= item.size delete(c.items, item.id) c.lru.Remove(last) } }