// Copyright (c) 2019 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cache import ( "container/list" "sync" "time" ) // LRU is a concurrent fixed size cache that evicts elements in LRU order as well as by TTL. type LRU struct { mux sync.Mutex byAccess *list.List byKey map[string]*list.Element maxSize int ttl time.Duration TimeNow func() time.Time onEvict EvictCallback } // NewLRU creates a new LRU cache with default options. func NewLRU(maxSize int) *LRU { return NewLRUWithOptions(maxSize, nil) } // NewLRUWithOptions creates a new LRU cache with the given options. func NewLRUWithOptions(maxSize int, opts *Options) *LRU { if opts == nil { opts = &Options{} } if opts.TimeNow == nil { opts.TimeNow = time.Now } return &LRU{ byAccess: list.New(), byKey: make(map[string]*list.Element, opts.InitialCapacity), ttl: opts.TTL, maxSize: maxSize, TimeNow: opts.TimeNow, onEvict: opts.OnEvict, } } // Get retrieves the value stored under the given key func (c *LRU) Get(key string) interface{} { c.mux.Lock() defer c.mux.Unlock() elt := c.byKey[key] if elt == nil { return nil } cacheEntry := elt.Value.(*cacheEntry) if !cacheEntry.expiration.IsZero() && c.TimeNow().After(cacheEntry.expiration) { // Entry has expired if c.onEvict != nil { c.onEvict(cacheEntry.key, cacheEntry.value) } c.byAccess.Remove(elt) delete(c.byKey, cacheEntry.key) return nil } c.byAccess.MoveToFront(elt) return cacheEntry.value } // Put puts a new value associated with a given key, returning the existing value (if present) func (c *LRU) Put(key string, value interface{}) interface{} { c.mux.Lock() defer c.mux.Unlock() elt := c.byKey[key] return c.putWithMutexHold(key, value, elt) } // CompareAndSwap puts a new value associated with a given key if existing value matches oldValue. // It returns itemInCache as the element in cache after the function is executed and replaced as true if value is replaced, false otherwise. func (c *LRU) CompareAndSwap(key string, oldValue, newValue interface{}) (itemInCache interface{}, replaced bool) { c.mux.Lock() defer c.mux.Unlock() elt := c.byKey[key] // If entry not found, old value should be nil if elt == nil && oldValue != nil { return nil, false } if elt != nil { // Entry found, compare it with that you expect. entry := elt.Value.(*cacheEntry) if entry.value != oldValue { return entry.value, false } } c.putWithMutexHold(key, newValue, elt) return newValue, true } // putWithMutexHold populates the cache and returns the inserted value. // Caller is expected to hold the c.mut mutex before calling. func (c *LRU) putWithMutexHold(key string, value interface{}, elt *list.Element) interface{} { if elt != nil { entry := elt.Value.(*cacheEntry) existing := entry.value entry.value = value if c.ttl != 0 { entry.expiration = c.TimeNow().Add(c.ttl) } c.byAccess.MoveToFront(elt) return existing } entry := &cacheEntry{ key: key, value: value, } if c.ttl != 0 { entry.expiration = c.TimeNow().Add(c.ttl) } c.byKey[key] = c.byAccess.PushFront(entry) for len(c.byKey) > c.maxSize { oldest := c.byAccess.Remove(c.byAccess.Back()).(*cacheEntry) if c.onEvict != nil { c.onEvict(oldest.key, oldest.value) } delete(c.byKey, oldest.key) } return nil } // Delete deletes a key, value pair associated with a key func (c *LRU) Delete(key string) { c.mux.Lock() defer c.mux.Unlock() elt := c.byKey[key] if elt != nil { entry := c.byAccess.Remove(elt).(*cacheEntry) if c.onEvict != nil { c.onEvict(entry.key, entry.value) } delete(c.byKey, key) } } // Size returns the number of entries currently in the lru, useful if cache is not full func (c *LRU) Size() int { c.mux.Lock() defer c.mux.Unlock() return len(c.byKey) } type cacheEntry struct { key string expiration time.Time value interface{} }