github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/link_cache.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"container/list"
     8  	"fmt"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  type linkIDFixed [LinkIDLen]byte
    14  
    15  // LinkCache is a cache of ChainLinks.
    16  // It is safe to use concurrently.
    17  type LinkCache struct {
    18  	sync.Mutex
    19  
    20  	cache        map[linkIDFixed]*list.Element
    21  	done         chan struct{}
    22  	shutdownOnce sync.Once
    23  
    24  	cleanWait time.Duration
    25  	maxSize   int
    26  
    27  	accessOrder *list.List
    28  }
    29  
    30  // NewLinkCache creates a LinkCache. When finished using this
    31  // LinkCache, call Shutdown on it to clean up.
    32  func NewLinkCache(maxSize int, cleanDur time.Duration) *LinkCache {
    33  	c := &LinkCache{
    34  		cache:       make(map[linkIDFixed]*list.Element),
    35  		done:        make(chan struct{}),
    36  		maxSize:     maxSize,
    37  		cleanWait:   cleanDur,
    38  		accessOrder: list.New(),
    39  	}
    40  	go c.periodic()
    41  	return c
    42  }
    43  
    44  // Get retrieves a ChainLink from the cache. If nothing
    45  // exists for this LinkID, it will return false for ok.
    46  func (c *LinkCache) Get(id LinkID) (link ChainLink, ok bool) {
    47  	c.Lock()
    48  	defer c.Unlock()
    49  	var linkID linkIDFixed
    50  	copy(linkID[:], id)
    51  	elt, ok := c.cache[linkID]
    52  	if ok {
    53  		link, ok := elt.Value.(ChainLink)
    54  		if !ok {
    55  			panic(fmt.Sprintf("invalid type in cache: %T", elt))
    56  		}
    57  		// move the element to the back (most recently accessed)
    58  		c.accessOrder.MoveToBack(elt)
    59  		return link.Copy(), true
    60  	}
    61  	return link, false
    62  }
    63  
    64  func (c *LinkCache) Put(m MetaContext, id LinkID, link ChainLink) {
    65  	c.Lock()
    66  	defer c.Unlock()
    67  	var linkID linkIDFixed
    68  	copy(linkID[:], id)
    69  
    70  	elt, ok := c.cache[linkID]
    71  	if ok {
    72  		// if this link already exists, remove it from
    73  		// the accessOrder list.
    74  		c.accessOrder.Remove(elt)
    75  	}
    76  	elt = c.accessOrder.PushBack(link)
    77  	c.cache[linkID] = elt
    78  }
    79  
    80  func (c *LinkCache) Mutate(id LinkID, f func(c *ChainLink)) {
    81  	c.Lock()
    82  	defer c.Unlock()
    83  	var linkID linkIDFixed
    84  	copy(linkID[:], id)
    85  	if elt, ok := c.cache[linkID]; ok {
    86  		if link, ok := elt.Value.(ChainLink); ok {
    87  			f(&link)
    88  			c.accessOrder.Remove(elt)
    89  			elt = c.accessOrder.PushBack(link)
    90  			c.cache[linkID] = elt
    91  		}
    92  	}
    93  }
    94  
    95  // Remove deletes a ChainLink from the cache.
    96  func (c *LinkCache) Remove(id LinkID) {
    97  	c.Lock()
    98  	defer c.Unlock()
    99  	var linkID linkIDFixed
   100  	copy(linkID[:], id)
   101  	elt, ok := c.cache[linkID]
   102  	if ok {
   103  		c.accessOrder.Remove(elt)
   104  	}
   105  	delete(c.cache, linkID)
   106  }
   107  
   108  // Len returns the number of ChainLinks cached.
   109  func (c *LinkCache) Len() int {
   110  	c.Lock()
   111  	defer c.Unlock()
   112  	return len(c.cache)
   113  }
   114  
   115  // Shutdown terminates the use of this cache.
   116  func (c *LinkCache) Shutdown() {
   117  	c.shutdownOnce.Do(func() { close(c.done) })
   118  }
   119  
   120  func (c *LinkCache) Clean() {
   121  	c.Lock()
   122  	defer c.Unlock()
   123  	delta := len(c.cache) - c.maxSize
   124  	for i := 0; i < delta; i++ {
   125  		// get the least recently used element
   126  		oldest := c.accessOrder.Front()
   127  
   128  		// get a fixed link id from it
   129  		link, ok := oldest.Value.(ChainLink)
   130  		if !ok {
   131  			panic(fmt.Sprintf("invalid type in cache: %T", oldest))
   132  		}
   133  		var linkID linkIDFixed
   134  		copy(linkID[:], link.id)
   135  
   136  		// remove the oldest element
   137  		c.accessOrder.Remove(oldest)
   138  
   139  		// and remove the ChainLink from the cache
   140  		delete(c.cache, linkID)
   141  	}
   142  }
   143  
   144  func (c *LinkCache) periodic() {
   145  	for {
   146  		select {
   147  		case <-c.done:
   148  			return
   149  		case <-time.After(c.cleanWait):
   150  			c.Clean()
   151  		}
   152  	}
   153  }