github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/go/util/sizecache/size_cache.go (about)

     1  // Copyright 2016 Attic Labs, Inc. All rights reserved.
     2  // Licensed under the Apache License, version 2.0:
     3  // http://www.apache.org/licenses/LICENSE-2.0
     4  
     5  package sizecache
     6  
     7  // SizeCache implements a simple LRU cache of interface{}-typed key-value pairs.
     8  // When items are added, the "size" of the item must be provided. LRU items will
     9  // be expired until the total of all items is below the specified size for the
    10  // SizeCache
    11  import (
    12  	"container/list"
    13  	"sync"
    14  
    15  	"github.com/attic-labs/noms/go/d"
    16  )
    17  
    18  type sizeCacheEntry struct {
    19  	size     uint64
    20  	lruEntry *list.Element
    21  	value    interface{}
    22  }
    23  
    24  type SizeCache struct {
    25  	totalSize uint64
    26  	maxSize   uint64
    27  	mu        sync.Mutex
    28  	lru       list.List
    29  	cache     map[interface{}]sizeCacheEntry
    30  	expireCb  func(elm interface{})
    31  }
    32  
    33  type ExpireCallback func(key interface{})
    34  
    35  // New creates a SizeCache that will hold up to |maxSize| item data.
    36  func New(maxSize uint64) *SizeCache {
    37  	return NewWithExpireCallback(maxSize, nil)
    38  }
    39  
    40  // NewWithExpireCallback creates a SizeCache that will hold up to |maxSize|
    41  // item data, and will call cb(key) when the item corresponding with that key
    42  // expires.
    43  func NewWithExpireCallback(maxSize uint64, cb ExpireCallback) *SizeCache {
    44  	return &SizeCache{
    45  		maxSize:  maxSize,
    46  		cache:    map[interface{}]sizeCacheEntry{},
    47  		expireCb: cb,
    48  	}
    49  }
    50  
    51  // entry() checks if the value is in the cache. If not in the cache, it returns an
    52  // empty sizeCacheEntry and false. It it is in the cache, it moves it to
    53  // to the back of lru and returns the entry and true.
    54  // Callers should have locked down the |c| with a call to c.mu.Lock() before
    55  // calling this entry().
    56  func (c *SizeCache) entry(key interface{}) (sizeCacheEntry, bool) {
    57  	entry, ok := c.cache[key]
    58  	if !ok {
    59  		return sizeCacheEntry{}, false
    60  	}
    61  	c.lru.MoveToBack(entry.lruEntry)
    62  	return entry, true
    63  }
    64  
    65  // Get checks the searches the cache for an entry. If it exists, it moves it's
    66  // lru entry to the back of the queue and returns (value, true). Otherwise, it
    67  // returns (nil, false).
    68  func (c *SizeCache) Get(key interface{}) (interface{}, bool) {
    69  	c.mu.Lock()
    70  	defer c.mu.Unlock()
    71  
    72  	if entry, ok := c.entry(key); ok {
    73  		return entry.value, true
    74  	}
    75  	return nil, false
    76  }
    77  
    78  // Add will add this element to the cache at the back of the queue as long it's
    79  // size does not exceed maxSize. If the addition of this entry causes the size of
    80  // the cache to exceed maxSize, the necessary entries at the front of the queue
    81  // will be deleted in order to keep the total cache size below maxSize.
    82  func (c *SizeCache) Add(key interface{}, size uint64, value interface{}) {
    83  	if size <= c.maxSize {
    84  		c.mu.Lock()
    85  		defer c.mu.Unlock()
    86  
    87  		if _, ok := c.entry(key); ok {
    88  			// this value is already in the cache; just return
    89  			return
    90  		}
    91  
    92  		newEl := c.lru.PushBack(key)
    93  		ce := sizeCacheEntry{size: size, lruEntry: newEl, value: value}
    94  		c.cache[key] = ce
    95  		c.totalSize += ce.size
    96  		for el := c.lru.Front(); el != nil && c.totalSize > c.maxSize; {
    97  			key1 := el.Value
    98  			ce, ok := c.cache[key1]
    99  			if !ok {
   100  				d.Panic("SizeCache is missing expected value")
   101  			}
   102  			next := el.Next()
   103  			delete(c.cache, key1)
   104  			c.totalSize -= ce.size
   105  			c.lru.Remove(el)
   106  			if c.expireCb != nil {
   107  				c.expireCb(key1)
   108  			}
   109  			el = next
   110  		}
   111  	}
   112  }
   113  
   114  // Drop will remove the element associated with the given key from the cache.
   115  func (c *SizeCache) Drop(key interface{}) {
   116  	c.mu.Lock()
   117  	defer c.mu.Unlock()
   118  
   119  	if entry, ok := c.entry(key); ok {
   120  		c.totalSize -= entry.size
   121  		c.lru.Remove(entry.lruEntry)
   122  		delete(c.cache, key)
   123  	}
   124  }