// Copyright (c) 2013 CloudFlare, Inc. package lrucache import ( "container/heap" "sync" "time" ) // Every element in the cache is linked to three data structures: // Table map, PriorityQueue heap ordered by expiry and a LruList list // ordered by decreasing popularity. type entry struct { element element // list element. value is a pointer to this entry key string // key is a key! value interface{} // expire time.Time // time when the item is expired. it's okay to be stale. index int // index for priority queue needs. -1 if entry is free } // LRUCache data structure. Never dereference it or copy it by // value. Always use it through a pointer. type LRUCache struct { lock sync.Mutex table map[string]*entry // all entries in table must be in lruList priorityQueue priorityQueue // some elements from table may be in priorityQueue lruList list // every entry is either used and resides in lruList freeList list // or free and is linked to freeList ExpireGracePeriod time.Duration // time after an expired entry is purged from cache (unless pushed out of LRU) } // Initialize the LRU cache instance. O(capacity) func (b *LRUCache) Init(capacity uint) { b.table = make(map[string]*entry, capacity) b.priorityQueue = make([]*entry, 0, capacity) b.lruList.Init() b.freeList.Init() heap.Init(&b.priorityQueue) // Reserve all the entries in one giant continous block of memory arrayOfEntries := make([]entry, capacity) for i := uint(0); i < capacity; i++ { e := &arrayOfEntries[i] e.element.Value = e e.index = -1 b.freeList.PushElementBack(&e.element) } } // Create new LRU cache instance. Allocate all the needed memory. O(capacity) func NewLRUCache(capacity uint) *LRUCache { b := &LRUCache{} b.Init(capacity) return b } // Give me the entry with lowest expiry field if it's before now. func (b *LRUCache) expiredEntry(now time.Time) *entry { if len(b.priorityQueue) == 0 { return nil } if now.IsZero() { // Fill it only when actually used. now = time.Now() } if e := b.priorityQueue[0]; e.expire.Before(now) { return e } return nil } // Give me the least used entry. func (b *LRUCache) leastUsedEntry() *entry { return b.lruList.Back().Value.(*entry) } func (b *LRUCache) freeSomeEntry(now time.Time) (e *entry, used bool) { if b.freeList.Len() > 0 { return b.freeList.Front().Value.(*entry), false } e = b.expiredEntry(now) if e != nil { return e, true } if b.lruList.Len() == 0 { return nil, false } return b.leastUsedEntry(), true } // Move entry from used/lru list to a free list. Clear the entry as well. func (b *LRUCache) removeEntry(e *entry) { if e.element.list != &b.lruList { panic("list lruList") } if e.index != -1 { heap.Remove(&b.priorityQueue, e.index) } b.lruList.Remove(&e.element) b.freeList.PushElementFront(&e.element) delete(b.table, e.key) e.key = "" e.value = nil } func (b *LRUCache) insertEntry(e *entry) { if e.element.list != &b.freeList { panic("list freeList") } if !e.expire.IsZero() { heap.Push(&b.priorityQueue, e) } b.freeList.Remove(&e.element) b.lruList.PushElementFront(&e.element) b.table[e.key] = e } func (b *LRUCache) touchEntry(e *entry) { b.lruList.MoveToFront(&e.element) } // Add an item to the cache overwriting existing one if it // exists. Allows specifing current time required to expire an item // when no more slots are used. O(log(n)) if expiry is set, O(1) when // clear. func (b *LRUCache) SetNow(key string, value interface{}, expire time.Time, now time.Time) { b.lock.Lock() defer b.lock.Unlock() var used bool e := b.table[key] if e != nil { used = true } else { e, used = b.freeSomeEntry(now) if e == nil { return } } if used { b.removeEntry(e) } e.key = key e.value = value e.expire = expire b.insertEntry(e) } // Add an item to the cache overwriting existing one if it // exists. O(log(n)) if expiry is set, O(1) when clear. func (b *LRUCache) Set(key string, value interface{}, expire time.Time) { b.SetNow(key, value, expire, time.Time{}) } // Get a key from the cache, possibly stale. Update its LRU score. O(1) func (b *LRUCache) Get(key string) (v interface{}, ok bool) { b.lock.Lock() defer b.lock.Unlock() e := b.table[key] if e == nil { return nil, false } b.touchEntry(e) return e.value, true } // Get a key from the cache, possibly stale. Don't modify its LRU score. O(1) func (b *LRUCache) GetQuiet(key string) (v interface{}, ok bool) { b.lock.Lock() defer b.lock.Unlock() e := b.table[key] if e == nil { return nil, false } return e.value, true } // Get a key from the cache, make sure it's not stale. Update its // LRU score. O(log(n)) if the item is expired. func (b *LRUCache) GetNotStale(key string) (value interface{}, ok bool) { return b.GetNotStaleNow(key, time.Now()) } // Get a key from the cache, make sure it's not stale. Update its // LRU score. O(log(n)) if the item is expired. func (b *LRUCache) GetNotStaleNow(key string, now time.Time) (value interface{}, ok bool) { b.lock.Lock() defer b.lock.Unlock() e := b.table[key] if e == nil { return nil, false } if e.expire.Before(now) { // Remove entries expired for more than a graceful period if b.ExpireGracePeriod == 0 || e.expire.Sub(now) > b.ExpireGracePeriod { b.removeEntry(e) } return nil, false } b.touchEntry(e) return e.value, true } // Get a key from the cache, possibly stale. Update its LRU // score. O(1) always. func (b *LRUCache) GetStale(key string) (value interface{}, ok, expired bool) { return b.GetStaleNow(key, time.Now()) } // Get a key from the cache, possibly stale. Update its LRU // score. O(1) always. func (b *LRUCache) GetStaleNow(key string, now time.Time) (value interface{}, ok, expired bool) { b.lock.Lock() defer b.lock.Unlock() e := b.table[key] if e == nil { return nil, false, false } b.touchEntry(e) return e.value, true, e.expire.Before(now) } // Get and remove a key from the cache. O(log(n)) if the item is using expiry, O(1) otherwise. func (b *LRUCache) Del(key string) (v interface{}, ok bool) { b.lock.Lock() defer b.lock.Unlock() e := b.table[key] if e == nil { return nil, false } value := e.value b.removeEntry(e) return value, true } // Evict all items from the cache. O(n*log(n)) func (b *LRUCache) Clear() int { b.lock.Lock() defer b.lock.Unlock() // First, remove entries that have expiry set l := len(b.priorityQueue) for i := 0; i < l; i++ { // This could be reduced to O(n). b.removeEntry(b.priorityQueue[0]) } // Second, remove all remaining entries r := b.lruList.Len() for i := 0; i < r; i++ { b.removeEntry(b.leastUsedEntry()) } return l + r } // Evict all the expired items. O(n*log(n)) func (b *LRUCache) Expire() int { return b.ExpireNow(time.Now()) } // Evict items that expire before `now`. O(n*log(n)) func (b *LRUCache) ExpireNow(now time.Time) int { b.lock.Lock() defer b.lock.Unlock() i := 0 for { e := b.expiredEntry(now) if e == nil { break } b.removeEntry(e) i += 1 } return i } // Number of entries used in the LRU func (b *LRUCache) Len() int { // yes. this stupid thing requires locking b.lock.Lock() defer b.lock.Unlock() return b.lruList.Len() } // Get the total capacity of the LRU func (b *LRUCache) Capacity() int { // yes. this stupid thing requires locking b.lock.Lock() defer b.lock.Unlock() return b.lruList.Len() + b.freeList.Len() }