github.com/auttaja/go-tlru@v0.0.0-20200823214418-2a1d18ce6d93/cache.go (about)

     1  package tlru
     2  
     3  import (
     4  	"container/list"
     5  	"runtime"
     6  	"sync"
     7  	"time"
     8  )
     9  
    10  // Defines an item in the cache.
    11  type cacheItem struct {
    12  	item      interface{}
    13  	size      int
    14  	destroyer *time.Timer
    15  	element   *list.Element
    16  }
    17  
    18  // Defines the cache and all required items.
    19  type Cache struct {
    20  	// Defines the attributes for the base cache.
    21  	m        sync.Mutex
    22  	keyList  *list.List
    23  	valueMap map[interface{}]*cacheItem
    24  	maxLen   int
    25  	maxBytes int
    26  
    27  	// The total size of items in the cache.
    28  	totalBytes int
    29  
    30  	// Define the duration of an item in the cache.
    31  	duration time.Duration
    32  }
    33  
    34  // Purge the first added item possible from the cache.
    35  // THIS FUNCTION IS NOT THREAD SAFE FOR PERFORMANCE REASONS (DOUBLE LOCKING)! BE CAREFUL!
    36  func (c *Cache) purgeFirst() {
    37  	f := c.keyList.Front()
    38  	if f == nil {
    39  		return
    40  	}
    41  	c.keyList.Remove(f)
    42  	item := c.valueMap[f.Value]
    43  	c.totalBytes -= item.size
    44  	item.destroyer.Stop()
    45  	delete(c.valueMap, f.Value)
    46  }
    47  
    48  // Get is used to try and get a interface from the cache.
    49  // The second boolean is meant to represent ok. If it's false, it was not in the cache.
    50  func (c *Cache) Get(Key interface{}) (item interface{}, ok bool) {
    51  	// Lock the mutex.
    52  	c.m.Lock()
    53  
    54  	// Try to get from the cache.
    55  	x, ok := c.valueMap[Key]
    56  
    57  	// If this isn't ok, we return here.
    58  	if !ok {
    59  		c.m.Unlock()
    60  		return nil, false
    61  	}
    62  
    63  	// Revive the key.
    64  	c.keyList.Remove(x.element)
    65  	x.element = c.keyList.PushBack(Key)
    66  	x.destroyer.Reset(c.duration)
    67  
    68  	// Unlock the mutex.
    69  	c.m.Unlock()
    70  
    71  	// Return the item.
    72  	return x.item, true
    73  }
    74  
    75  // Used to generate a destruction function for a item.
    76  func (c *Cache) destroyItem(Key interface{}, timer bool) func() {
    77  	return func() {
    78  		// Lock the mutex.
    79  		c.m.Lock()
    80  
    81  		// Delete the item from the cache.
    82  		item := c.valueMap[Key]
    83  		c.keyList.Remove(item.element)
    84  		delete(c.valueMap, Key)
    85  		if !timer {
    86  			item.destroyer.Stop()
    87  		}
    88  		c.totalBytes -= item.size
    89  
    90  		// Unlock the mutex.
    91  		c.m.Unlock()
    92  	}
    93  }
    94  
    95  // Delete is used to delete an option from the cache.
    96  func (c *Cache) Delete(Key interface{}) {
    97  	c.destroyItem(Key, false)()
    98  }
    99  
   100  // Erase is used to erase the cache.
   101  func (c *Cache) Erase() {
   102  	c.m.Lock()
   103  	c.keyList = list.New()
   104  	for _, v := range c.valueMap {
   105  		v.destroyer.Stop()
   106  	}
   107  	c.valueMap = map[interface{}]*cacheItem{}
   108  	c.m.Unlock()
   109  	c.totalBytes = 0
   110  	runtime.GC()
   111  }
   112  
   113  // Set is used to set a key/value interface in the cache.
   114  func (c *Cache) Set(Key, Value interface{}) {
   115  	// Lock the mutex.
   116  	c.m.Lock()
   117  
   118  	// Check if the key already exists and the length.
   119  	item, exists := c.valueMap[Key]
   120  
   121  	// Get the total size.
   122  	var total int
   123  	if c.maxBytes != 0 {
   124  		total = int(sizeof(Value))
   125  		if total > c.maxBytes {
   126  			// Don't cache this.
   127  			c.m.Unlock()
   128  			return
   129  		}
   130  	}
   131  
   132  	// If the key already exists, we should revive the key. If not, we should push it and set a timer in the map.
   133  	if exists {
   134  		c.keyList.Remove(item.element)
   135  		item.element = c.keyList.PushBack(Key)
   136  		item.destroyer.Reset(c.duration)
   137  		item.item = Value
   138  	} else {
   139  		// If the length is the max length, remove one.
   140  		l := len(c.valueMap)
   141  		if l == c.maxLen && c.maxLen != 0 {
   142  			c.purgeFirst()
   143  		}
   144  
   145  		// Set the cache item.
   146  		el := c.keyList.PushBack(Key)
   147  		item = &cacheItem{
   148  			item:      Value,
   149  			destroyer: time.AfterFunc(c.duration, c.destroyItem(Key, true)),
   150  			size:      total,
   151  			element:   el,
   152  		}
   153  		c.valueMap[Key] = item
   154  		c.totalBytes += total
   155  	}
   156  
   157  	// Ensure max bytes is greater than or equal to total bytes.
   158  	for c.totalBytes > c.maxBytes {
   159  		// Purge the first item.
   160  		c.purgeFirst()
   161  	}
   162  
   163  	// Unlock the mutex.
   164  	c.m.Unlock()
   165  }
   166  
   167  // NewCache is used to create the cache.
   168  // Setting MaxLength of MaxBytes to 0 will mean unlimited.
   169  func NewCache(MaxLength, MaxBytes int, Duration time.Duration) *Cache {
   170  	return &Cache{
   171  		keyList:  list.New(),
   172  		valueMap: map[interface{}]*cacheItem{},
   173  		maxLen:   MaxLength,
   174  		maxBytes: MaxBytes,
   175  		duration: Duration,
   176  	}
   177  }