github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/lib/cache/cache.go (about)

     1  // Package cache implements a simple cache where the entries are
     2  // expired after a given time (5 minutes of disuse by default).
     3  package cache
     4  
     5  import (
     6  	"sync"
     7  	"time"
     8  )
     9  
    10  // Cache holds values indexed by string, but expired after a given (5
    11  // minutes by default).
    12  type Cache struct {
    13  	mu             sync.Mutex
    14  	cache          map[string]*cacheEntry
    15  	expireRunning  bool
    16  	expireDuration time.Duration // expire the cache entry when it is older than this
    17  	expireInterval time.Duration // interval to run the cache expire
    18  }
    19  
    20  // New creates a new cache with the default expire duration and interval
    21  func New() *Cache {
    22  	return &Cache{
    23  		cache:          map[string]*cacheEntry{},
    24  		expireRunning:  false,
    25  		expireDuration: 300 * time.Second,
    26  		expireInterval: 60 * time.Second,
    27  	}
    28  }
    29  
    30  // cacheEntry is stored in the cache
    31  type cacheEntry struct {
    32  	value    interface{} // cached item
    33  	err      error       // creation error
    34  	key      string      // key
    35  	lastUsed time.Time   // time used for expiry
    36  	pinCount int         // non zero if the entry should not be removed
    37  }
    38  
    39  // CreateFunc is called to create new values.  If the create function
    40  // returns an error it will be cached if ok is true, otherwise the
    41  // error will just be returned, allowing negative caching if required.
    42  type CreateFunc func(key string) (value interface{}, ok bool, error error)
    43  
    44  // used marks an entry as accessed now and kicks the expire timer off
    45  // should be called with the lock held
    46  func (c *Cache) used(entry *cacheEntry) {
    47  	entry.lastUsed = time.Now()
    48  	if !c.expireRunning {
    49  		time.AfterFunc(c.expireInterval, c.cacheExpire)
    50  		c.expireRunning = true
    51  	}
    52  }
    53  
    54  // Get gets a value named key either from the cache or creates it
    55  // afresh with the create function.
    56  func (c *Cache) Get(key string, create CreateFunc) (value interface{}, err error) {
    57  	c.mu.Lock()
    58  	entry, ok := c.cache[key]
    59  	if !ok {
    60  		c.mu.Unlock() // Unlock in case Get is called recursively
    61  		value, ok, err = create(key)
    62  		if err != nil && !ok {
    63  			return value, err
    64  		}
    65  		entry = &cacheEntry{
    66  			value: value,
    67  			key:   key,
    68  			err:   err,
    69  		}
    70  		c.mu.Lock()
    71  		c.cache[key] = entry
    72  	}
    73  	defer c.mu.Unlock()
    74  	c.used(entry)
    75  	return entry.value, entry.err
    76  }
    77  
    78  func (c *Cache) addPin(key string, count int) {
    79  	c.mu.Lock()
    80  	entry, ok := c.cache[key]
    81  	if ok {
    82  		entry.pinCount += count
    83  		c.used(entry)
    84  	}
    85  	c.mu.Unlock()
    86  }
    87  
    88  // Pin a value in the cache if it exists
    89  func (c *Cache) Pin(key string) {
    90  	c.addPin(key, 1)
    91  }
    92  
    93  // Unpin a value in the cache if it exists
    94  func (c *Cache) Unpin(key string) {
    95  	c.addPin(key, -1)
    96  }
    97  
    98  // Put puts a value named key into the cache
    99  func (c *Cache) Put(key string, value interface{}) {
   100  	c.mu.Lock()
   101  	defer c.mu.Unlock()
   102  	entry := &cacheEntry{
   103  		value: value,
   104  		key:   key,
   105  	}
   106  	c.used(entry)
   107  	c.cache[key] = entry
   108  }
   109  
   110  // GetMaybe returns the key and true if found, nil and false if not
   111  func (c *Cache) GetMaybe(key string) (value interface{}, found bool) {
   112  	c.mu.Lock()
   113  	defer c.mu.Unlock()
   114  	entry, found := c.cache[key]
   115  	if !found {
   116  		return nil, found
   117  	}
   118  	c.used(entry)
   119  	return entry.value, found
   120  }
   121  
   122  // Rename renames the item at oldKey to newKey.
   123  //
   124  // If there was an existing item at newKey then it takes precedence
   125  // and is returned otherwise the item (if any) at oldKey is returned.
   126  func (c *Cache) Rename(oldKey, newKey string) (value interface{}, found bool) {
   127  	c.mu.Lock()
   128  	if newEntry, newFound := c.cache[newKey]; newFound {
   129  		// If new entry is found use that
   130  		delete(c.cache, oldKey)
   131  		value, found = newEntry.value, newFound
   132  		c.used(newEntry)
   133  	} else if oldEntry, oldFound := c.cache[oldKey]; oldFound {
   134  		// If old entry is found rename it to new and use that
   135  		c.cache[newKey] = oldEntry
   136  		delete(c.cache, oldKey)
   137  		c.used(oldEntry)
   138  		value, found = oldEntry.value, oldFound
   139  	}
   140  	c.mu.Unlock()
   141  	return value, found
   142  }
   143  
   144  // cacheExpire expires any entries that haven't been used recently
   145  func (c *Cache) cacheExpire() {
   146  	c.mu.Lock()
   147  	defer c.mu.Unlock()
   148  	now := time.Now()
   149  	for key, entry := range c.cache {
   150  		if entry.pinCount <= 0 && now.Sub(entry.lastUsed) > c.expireDuration {
   151  			delete(c.cache, key)
   152  		}
   153  	}
   154  	if len(c.cache) != 0 {
   155  		time.AfterFunc(c.expireInterval, c.cacheExpire)
   156  		c.expireRunning = true
   157  	} else {
   158  		c.expireRunning = false
   159  	}
   160  }
   161  
   162  // Clear removes everything from the cache
   163  func (c *Cache) Clear() {
   164  	c.mu.Lock()
   165  	for k := range c.cache {
   166  		delete(c.cache, k)
   167  	}
   168  	c.mu.Unlock()
   169  }
   170  
   171  // Entries returns the number of entries in the cache
   172  func (c *Cache) Entries() int {
   173  	c.mu.Lock()
   174  	entries := len(c.cache)
   175  	c.mu.Unlock()
   176  	return entries
   177  }