246 lines
5.4 KiB
Go
246 lines
5.4 KiB
Go
// Copyright (c) 2012-2019 Patrick Mylund Nielsen and the go-cache contributors
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Copyright 2022 The FluxCD contributors. All rights reserved.
|
|
// This package provides an in-memory cache
|
|
// derived from the https://github.com/patrickmn/go-cache
|
|
// package
|
|
// It has been modified in order to keep a small set of functions
|
|
// and to add a maxItems parameter in order to limit the number of,
|
|
// and thus the size of, items in the cache.
|
|
|
|
package cache
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Cache is a thread-safe in-memory key/value store.
|
|
type Cache struct {
|
|
*cache
|
|
}
|
|
|
|
// Item is an item stored in the cache.
|
|
type Item struct {
|
|
// Object is the item's value.
|
|
Object interface{}
|
|
// Expiration is the item's expiration time.
|
|
Expiration int64
|
|
}
|
|
|
|
type cache struct {
|
|
// Items holds the elements in the cache.
|
|
Items map[string]Item
|
|
// MaxItems is the maximum number of items the cache can hold.
|
|
MaxItems int
|
|
mu sync.RWMutex
|
|
janitor *janitor
|
|
}
|
|
|
|
// ItemCount returns the number of items in the cache.
|
|
// This may include items that have expired, but have not yet been cleaned up.
|
|
func (c *cache) ItemCount() int {
|
|
c.mu.RLock()
|
|
n := len(c.Items)
|
|
c.mu.RUnlock()
|
|
return n
|
|
}
|
|
|
|
func (c *cache) set(key string, value interface{}, expiration time.Duration) {
|
|
var e int64
|
|
if expiration > 0 {
|
|
e = time.Now().Add(expiration).UnixNano()
|
|
}
|
|
|
|
c.Items[key] = Item{
|
|
Object: value,
|
|
Expiration: e,
|
|
}
|
|
}
|
|
|
|
// Set adds an item to the cache, replacing any existing item.
|
|
// If expiration is zero, the item never expires.
|
|
// If the cache is full, Set will return an error.
|
|
func (c *cache) Set(key string, value interface{}, expiration time.Duration) error {
|
|
c.mu.Lock()
|
|
_, found := c.Items[key]
|
|
if found {
|
|
c.set(key, value, expiration)
|
|
c.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
if c.MaxItems > 0 && len(c.Items) < c.MaxItems {
|
|
c.set(key, value, expiration)
|
|
c.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
c.mu.Unlock()
|
|
return fmt.Errorf("Cache is full")
|
|
}
|
|
|
|
// Add an item to the cache, existing items will not be overwritten.
|
|
// To overwrite existing items, use Set.
|
|
// If the cache is full, Add will return an error.
|
|
func (c *cache) Add(key string, value interface{}, expiration time.Duration) error {
|
|
c.mu.Lock()
|
|
_, found := c.Items[key]
|
|
if found {
|
|
c.mu.Unlock()
|
|
return fmt.Errorf("Item %s already exists", key)
|
|
}
|
|
|
|
if c.MaxItems > 0 && len(c.Items) < c.MaxItems {
|
|
c.set(key, value, expiration)
|
|
c.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
c.mu.Unlock()
|
|
return fmt.Errorf("Cache is full")
|
|
}
|
|
|
|
// Get an item from the cache. Returns the item or nil, and a bool indicating
|
|
// whether the key was found.
|
|
func (c *cache) Get(key string) (interface{}, bool) {
|
|
c.mu.RLock()
|
|
item, found := c.Items[key]
|
|
if !found {
|
|
c.mu.RUnlock()
|
|
return nil, false
|
|
}
|
|
if item.Expiration > 0 {
|
|
if item.Expiration < time.Now().UnixNano() {
|
|
c.mu.RUnlock()
|
|
return nil, false
|
|
}
|
|
}
|
|
c.mu.RUnlock()
|
|
return item.Object, true
|
|
}
|
|
|
|
// Delete an item from the cache. Does nothing if the key is not in the cache.
|
|
func (c *cache) Delete(key string) {
|
|
c.mu.Lock()
|
|
delete(c.Items, key)
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// Clear all items from the cache.
|
|
// This reallocates the underlying array holding the items,
|
|
// so that the memory used by the items is reclaimed.
|
|
func (c *cache) Clear() {
|
|
c.mu.Lock()
|
|
c.Items = make(map[string]Item)
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// HasExpired returns true if the item has expired.
|
|
func (c *cache) HasExpired(key string) bool {
|
|
c.mu.RLock()
|
|
item, ok := c.Items[key]
|
|
if !ok {
|
|
c.mu.RUnlock()
|
|
return true
|
|
}
|
|
if item.Expiration > 0 {
|
|
if item.Expiration < time.Now().UnixNano() {
|
|
c.mu.RUnlock()
|
|
return true
|
|
}
|
|
}
|
|
c.mu.RUnlock()
|
|
return false
|
|
}
|
|
|
|
// SetExpiration sets the expiration for the given key.
|
|
// Does nothing if the key is not in the cache.
|
|
func (c *cache) SetExpiration(key string, expiration time.Duration) {
|
|
c.mu.Lock()
|
|
item, ok := c.Items[key]
|
|
if ok {
|
|
item.Expiration = time.Now().Add(expiration).UnixNano()
|
|
c.Items[key] = item
|
|
}
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// GetExpiration returns the expiration for the given key.
|
|
// Returns zero if the key is not in the cache or the item
|
|
// has already expired.
|
|
func (c *cache) GetExpiration(key string) time.Duration {
|
|
c.mu.RLock()
|
|
item, ok := c.Items[key]
|
|
if !ok {
|
|
c.mu.RUnlock()
|
|
return 0
|
|
}
|
|
if item.Expiration > 0 {
|
|
if item.Expiration < time.Now().UnixNano() {
|
|
c.mu.RUnlock()
|
|
return 0
|
|
}
|
|
}
|
|
c.mu.RUnlock()
|
|
return time.Duration(item.Expiration - time.Now().UnixNano())
|
|
}
|
|
|
|
// DeleteExpired deletes all expired items from the cache.
|
|
func (c *cache) DeleteExpired() {
|
|
c.mu.Lock()
|
|
for k, v := range c.Items {
|
|
if v.Expiration > 0 && v.Expiration < time.Now().UnixNano() {
|
|
delete(c.Items, k)
|
|
}
|
|
}
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
type janitor struct {
|
|
interval time.Duration
|
|
stop chan bool
|
|
}
|
|
|
|
func (j *janitor) run(c *cache) {
|
|
ticker := time.NewTicker(j.interval)
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
c.DeleteExpired()
|
|
case <-j.stop:
|
|
ticker.Stop()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func stopJanitor(c *Cache) {
|
|
c.janitor.stop <- true
|
|
}
|
|
|
|
// New creates a new cache with the given configuration.
|
|
func New(maxItems int, interval time.Duration) *Cache {
|
|
c := &cache{
|
|
Items: make(map[string]Item),
|
|
MaxItems: maxItems,
|
|
janitor: &janitor{
|
|
interval: interval,
|
|
stop: make(chan bool),
|
|
},
|
|
}
|
|
|
|
C := &Cache{c}
|
|
|
|
if interval > 0 {
|
|
go c.janitor.run(c)
|
|
runtime.SetFinalizer(C, stopJanitor)
|
|
}
|
|
|
|
return C
|
|
}
|