github.com/bhojpur/cache@v0.0.4/pkg/engine/lru_cache.go (about)

     1  package engine
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  // The implementation borrows heavily from SmallLRUCache
    24  // (originally by Nathan Schrenk). The object maintains a doubly-linked list of
    25  // elements. When an element is accessed, it is promoted to the head of the
    26  // list. When space is needed, the element at the tail of the list
    27  // (the least recently used element) is evicted.
    28  
    29  import (
    30  	"container/list"
    31  	"sync"
    32  	"time"
    33  )
    34  
    35  var _ Cache = &LRUCache{}
    36  
    37  // LRUCache is a typical LRU cache implementation.  If the cache
    38  // reaches the capacity, the least recently used item is deleted from
    39  // the cache. Note the capacity is not the number of items, but the
    40  // total sum of the CachedSize() of each item.
    41  type LRUCache struct {
    42  	mu sync.Mutex
    43  
    44  	// list & table contain *entry objects.
    45  	list  *list.List
    46  	table map[string]*list.Element
    47  	cost  func(interface{}) int64
    48  
    49  	size      int64
    50  	capacity  int64
    51  	evictions int64
    52  }
    53  
    54  // Item is what is stored in the cache
    55  type Item struct {
    56  	Key   string
    57  	Value interface{}
    58  }
    59  
    60  type entry struct {
    61  	key          string
    62  	value        interface{}
    63  	size         int64
    64  	timeAccessed time.Time
    65  }
    66  
    67  // NewLRUCache creates a new empty cache with the given capacity.
    68  func NewLRUCache(capacity int64, cost func(interface{}) int64) *LRUCache {
    69  	return &LRUCache{
    70  		list:     list.New(),
    71  		table:    make(map[string]*list.Element),
    72  		capacity: capacity,
    73  		cost:     cost,
    74  	}
    75  }
    76  
    77  // Get returns a value from the cache, and marks the entry as most
    78  // recently used.
    79  func (lru *LRUCache) Get(key string) (v interface{}, ok bool) {
    80  	lru.mu.Lock()
    81  	defer lru.mu.Unlock()
    82  
    83  	element := lru.table[key]
    84  	if element == nil {
    85  		return nil, false
    86  	}
    87  	lru.moveToFront(element)
    88  	return element.Value.(*entry).value, true
    89  }
    90  
    91  // Set sets a value in the cache.
    92  func (lru *LRUCache) Set(key string, value interface{}) bool {
    93  	lru.mu.Lock()
    94  	defer lru.mu.Unlock()
    95  
    96  	if element := lru.table[key]; element != nil {
    97  		lru.updateInplace(element, value)
    98  	} else {
    99  		lru.addNew(key, value)
   100  	}
   101  	// the LRU cache cannot fail to insert items; it always returns true
   102  	return true
   103  }
   104  
   105  // Delete removes an entry from the cache, and returns if the entry existed.
   106  func (lru *LRUCache) delete(key string) bool {
   107  	lru.mu.Lock()
   108  	defer lru.mu.Unlock()
   109  
   110  	element := lru.table[key]
   111  	if element == nil {
   112  		return false
   113  	}
   114  
   115  	lru.list.Remove(element)
   116  	delete(lru.table, key)
   117  	lru.size -= element.Value.(*entry).size
   118  	return true
   119  }
   120  
   121  // Delete removes an entry from the cache
   122  func (lru *LRUCache) Delete(key string) {
   123  	lru.delete(key)
   124  }
   125  
   126  // Clear will clear the entire cache.
   127  func (lru *LRUCache) Clear() {
   128  	lru.mu.Lock()
   129  	defer lru.mu.Unlock()
   130  
   131  	lru.list.Init()
   132  	lru.table = make(map[string]*list.Element)
   133  	lru.size = 0
   134  }
   135  
   136  // Len returns the size of the cache (in entries)
   137  func (lru *LRUCache) Len() int {
   138  	lru.mu.Lock()
   139  	defer lru.mu.Unlock()
   140  	return lru.list.Len()
   141  }
   142  
   143  // SetCapacity will set the capacity of the cache. If the capacity is
   144  // smaller, and the current cache size exceed that capacity, the cache
   145  // will be shrank.
   146  func (lru *LRUCache) SetCapacity(capacity int64) {
   147  	lru.mu.Lock()
   148  	defer lru.mu.Unlock()
   149  
   150  	lru.capacity = capacity
   151  	lru.checkCapacity()
   152  }
   153  
   154  // Wait is a no-op in the LRU cache
   155  func (lru *LRUCache) Wait() {}
   156  
   157  // UsedCapacity returns the size of the cache (in bytes)
   158  func (lru *LRUCache) UsedCapacity() int64 {
   159  	return lru.size
   160  }
   161  
   162  // MaxCapacity returns the cache maximum capacity.
   163  func (lru *LRUCache) MaxCapacity() int64 {
   164  	lru.mu.Lock()
   165  	defer lru.mu.Unlock()
   166  	return lru.capacity
   167  }
   168  
   169  // Evictions returns the number of evictions
   170  func (lru *LRUCache) Evictions() int64 {
   171  	lru.mu.Lock()
   172  	defer lru.mu.Unlock()
   173  	return lru.evictions
   174  }
   175  
   176  // ForEach yields all the values for the cache, ordered from most recently
   177  // used to least recently used.
   178  func (lru *LRUCache) ForEach(callback func(value interface{}) bool) {
   179  	lru.mu.Lock()
   180  	defer lru.mu.Unlock()
   181  
   182  	for e := lru.list.Front(); e != nil; e = e.Next() {
   183  		v := e.Value.(*entry)
   184  		if !callback(v.value) {
   185  			break
   186  		}
   187  	}
   188  }
   189  
   190  // Items returns all the values for the cache, ordered from most recently
   191  // used to least recently used.
   192  func (lru *LRUCache) Items() []Item {
   193  	lru.mu.Lock()
   194  	defer lru.mu.Unlock()
   195  
   196  	items := make([]Item, 0, lru.list.Len())
   197  	for e := lru.list.Front(); e != nil; e = e.Next() {
   198  		v := e.Value.(*entry)
   199  		items = append(items, Item{Key: v.key, Value: v.value})
   200  	}
   201  	return items
   202  }
   203  
   204  func (lru *LRUCache) updateInplace(element *list.Element, value interface{}) {
   205  	valueSize := lru.cost(value)
   206  	sizeDiff := valueSize - element.Value.(*entry).size
   207  	element.Value.(*entry).value = value
   208  	element.Value.(*entry).size = valueSize
   209  	lru.size += sizeDiff
   210  	lru.moveToFront(element)
   211  	lru.checkCapacity()
   212  }
   213  
   214  func (lru *LRUCache) moveToFront(element *list.Element) {
   215  	lru.list.MoveToFront(element)
   216  	element.Value.(*entry).timeAccessed = time.Now()
   217  }
   218  
   219  func (lru *LRUCache) addNew(key string, value interface{}) {
   220  	newEntry := &entry{key, value, lru.cost(value), time.Now()}
   221  	element := lru.list.PushFront(newEntry)
   222  	lru.table[key] = element
   223  	lru.size += newEntry.size
   224  	lru.checkCapacity()
   225  }
   226  
   227  func (lru *LRUCache) checkCapacity() {
   228  	// Partially duplicated from Delete
   229  	for lru.size > lru.capacity {
   230  		delElem := lru.list.Back()
   231  		delValue := delElem.Value.(*entry)
   232  		lru.list.Remove(delElem)
   233  		delete(lru.table, delValue.key)
   234  		lru.size -= delValue.size
   235  		lru.evictions++
   236  	}
   237  }