github.com/coyove/common@v0.0.0-20240403014525-f70e643f9de8/lru/lru.go (about)

     1  package lru
     2  
     3  import (
     4  	"container/list"
     5  	"fmt"
     6  	"sync"
     7  )
     8  
     9  type Cache struct {
    10  	// OnEvicted is called when an entry is going to be purged from the cache.
    11  	OnEvicted func(key Key, value interface{})
    12  
    13  	maxWeight int64
    14  	curWeight int64
    15  
    16  	ll    *list.List
    17  	cache map[interface{}]*list.Element
    18  
    19  	sync.Mutex
    20  }
    21  
    22  // A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators
    23  type Key interface{}
    24  
    25  type entry struct {
    26  	key    Key
    27  	value  interface{}
    28  	hits   int64
    29  	weight int64
    30  }
    31  
    32  var ErrWeightTooBig = fmt.Errorf("weight can't be held by the cache")
    33  
    34  // NewCache creates a new Cache.
    35  func NewCache(maxWeight int64) *Cache {
    36  	return &Cache{
    37  		maxWeight: maxWeight,
    38  		ll:        list.New(),
    39  		cache:     make(map[interface{}]*list.Element),
    40  	}
    41  }
    42  
    43  // Clear clears the cache
    44  func (c *Cache) Clear() {
    45  	c.Lock()
    46  	c.ll = list.New()
    47  	c.cache = make(map[interface{}]*list.Element)
    48  	c.Unlock()
    49  }
    50  
    51  // Info iterates the cache
    52  func (c *Cache) Info(callback func(Key, interface{}, int64, int64)) {
    53  	c.Lock()
    54  
    55  	for f := c.ll.Front(); f != nil; f = f.Next() {
    56  		e := f.Value.(*entry)
    57  		callback(e.key, e.value, e.hits, e.weight)
    58  	}
    59  
    60  	c.Unlock()
    61  }
    62  
    63  // Add adds a value to the cache with weight = 1.
    64  func (c *Cache) Add(key Key, value interface{}) error {
    65  	return c.AddWeight(key, value, 1)
    66  }
    67  
    68  // AddWeight adds a value to the cache with weight.
    69  func (c *Cache) AddWeight(key Key, value interface{}, weight int64) error {
    70  	if weight > c.maxWeight || weight < 1 {
    71  		return ErrWeightTooBig
    72  	}
    73  
    74  	c.Lock()
    75  	defer c.Unlock()
    76  
    77  	controlWeight := func() {
    78  		if c.maxWeight == 0 {
    79  			return
    80  		}
    81  
    82  		for c.curWeight > c.maxWeight {
    83  			if ele := c.ll.Back(); ele != nil {
    84  				c.removeElement(ele, true)
    85  			} else {
    86  				panic("shouldn't happen")
    87  			}
    88  		}
    89  		// Since weight <= c.maxWeight, we will always reach here without problems
    90  	}
    91  
    92  	if ee, ok := c.cache[key]; ok {
    93  		e := ee.Value.(*entry)
    94  		c.ll.MoveToFront(ee)
    95  		diff := weight - e.weight
    96  		e.weight = weight
    97  		e.value = value
    98  		e.hits++
    99  
   100  		c.curWeight += diff
   101  		controlWeight()
   102  		return nil
   103  	}
   104  
   105  	c.curWeight += weight
   106  	ele := c.ll.PushFront(&entry{key, value, 1, weight})
   107  	c.cache[key] = ele
   108  	controlWeight()
   109  
   110  	if c.curWeight < 0 {
   111  		panic("too many entries, really?")
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  // Get gets a key
   118  func (c *Cache) Get(key Key) (value interface{}, ok bool) {
   119  	c.Lock()
   120  	defer c.Unlock()
   121  
   122  	if ele, hit := c.cache[key]; hit {
   123  		e := ele.Value.(*entry)
   124  		e.hits++
   125  		c.ll.MoveToFront(ele)
   126  		return e.value, true
   127  	}
   128  
   129  	return
   130  }
   131  
   132  // GetEx returns the extra info of the given key
   133  func (c *Cache) GetEx(key Key) (hits int64, weight int64, ok bool) {
   134  	c.Lock()
   135  	defer c.Unlock()
   136  
   137  	if ele, hit := c.cache[key]; hit {
   138  		return ele.Value.(*entry).hits, ele.Value.(*entry).weight, true
   139  	}
   140  
   141  	return
   142  }
   143  
   144  // Remove removes the given key from the cache.
   145  func (c *Cache) Remove(key Key) {
   146  	c.Lock()
   147  	c.remove(key, true)
   148  	c.Unlock()
   149  }
   150  
   151  // RemoveSlient removes the given key without triggering OnEvicted
   152  func (c *Cache) RemoveSlient(key Key) {
   153  	c.Lock()
   154  	c.remove(key, false)
   155  	c.Unlock()
   156  }
   157  
   158  // Len returns the number of items in the cache.
   159  func (c *Cache) Len() (len int) {
   160  	c.Lock()
   161  	len = c.ll.Len()
   162  	c.Unlock()
   163  	return
   164  }
   165  
   166  // MaxWeight returns max weight
   167  func (c *Cache) MaxWeight() int64 {
   168  	return c.maxWeight
   169  }
   170  
   171  // Weight returns current weight
   172  func (c *Cache) Weight() int64 {
   173  	return c.curWeight
   174  }
   175  
   176  func (c *Cache) remove(key Key, doCallback bool) {
   177  	if ele, hit := c.cache[key]; hit {
   178  		c.removeElement(ele, doCallback)
   179  	}
   180  }
   181  
   182  func (c *Cache) removeElement(e *list.Element, doCallback bool) {
   183  	kv := e.Value.(*entry)
   184  
   185  	if c.OnEvicted != nil && doCallback {
   186  		c.OnEvicted(kv.key, kv.value)
   187  	}
   188  
   189  	c.ll.Remove(e)
   190  	c.curWeight -= e.Value.(*entry).weight
   191  	delete(c.cache, kv.key)
   192  }