mirror of https://github.com/grafana/loki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
9.4 KiB
350 lines
9.4 KiB
package cache
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Code-Hex/go-generics-cache/policy/clock"
|
|
"github.com/Code-Hex/go-generics-cache/policy/fifo"
|
|
"github.com/Code-Hex/go-generics-cache/policy/lfu"
|
|
"github.com/Code-Hex/go-generics-cache/policy/lru"
|
|
"github.com/Code-Hex/go-generics-cache/policy/mru"
|
|
"github.com/Code-Hex/go-generics-cache/policy/simple"
|
|
)
|
|
|
|
// Interface is a common-cache interface.
|
|
type Interface[K comparable, V any] interface {
|
|
// Get looks up a key's value from the cache.
|
|
Get(key K) (value V, ok bool)
|
|
// Set sets a value to the cache with key. replacing any existing value.
|
|
Set(key K, val V)
|
|
// Keys returns the keys of the cache. The order is relied on algorithms.
|
|
Keys() []K
|
|
// Delete deletes the item with provided key from the cache.
|
|
Delete(key K)
|
|
// Len returns the number of items in the cache.
|
|
Len() int
|
|
}
|
|
|
|
var (
|
|
_ = []Interface[struct{}, any]{
|
|
(*simple.Cache[struct{}, any])(nil),
|
|
(*lru.Cache[struct{}, any])(nil),
|
|
(*lfu.Cache[struct{}, any])(nil),
|
|
(*fifo.Cache[struct{}, any])(nil),
|
|
(*mru.Cache[struct{}, any])(nil),
|
|
(*clock.Cache[struct{}, any])(nil),
|
|
}
|
|
)
|
|
|
|
// Item is an item
|
|
type Item[K comparable, V any] struct {
|
|
Key K
|
|
Value V
|
|
Expiration time.Time
|
|
InitialReferenceCount int
|
|
}
|
|
|
|
func (item *Item[K, V]) hasExpiration() bool {
|
|
return !item.Expiration.IsZero()
|
|
}
|
|
|
|
// Expired returns true if the item has expired.
|
|
func (item *Item[K, V]) Expired() bool {
|
|
if !item.hasExpiration() {
|
|
return false
|
|
}
|
|
return nowFunc().After(item.Expiration)
|
|
}
|
|
|
|
// GetReferenceCount returns reference count to be used when setting
|
|
// the cache item for the first time.
|
|
func (item *Item[K, V]) GetReferenceCount() int {
|
|
return item.InitialReferenceCount
|
|
}
|
|
|
|
var nowFunc = time.Now
|
|
|
|
// ItemOption is an option for cache item.
|
|
type ItemOption func(*itemOptions)
|
|
|
|
type itemOptions struct {
|
|
expiration time.Time // default none
|
|
referenceCount int
|
|
}
|
|
|
|
// WithExpiration is an option to set expiration time for any items.
|
|
// If the expiration is zero or negative value, it treats as w/o expiration.
|
|
func WithExpiration(exp time.Duration) ItemOption {
|
|
return func(o *itemOptions) {
|
|
o.expiration = nowFunc().Add(exp)
|
|
}
|
|
}
|
|
|
|
// WithReferenceCount is an option to set reference count for any items.
|
|
// This option is only applicable to cache policies that have a reference count (e.g., Clock, LFU).
|
|
// referenceCount specifies the reference count value to set for the cache item.
|
|
//
|
|
// the default is 1.
|
|
func WithReferenceCount(referenceCount int) ItemOption {
|
|
return func(o *itemOptions) {
|
|
o.referenceCount = referenceCount
|
|
}
|
|
}
|
|
|
|
// newItem creates a new item with specified any options.
|
|
func newItem[K comparable, V any](key K, val V, opts ...ItemOption) *Item[K, V] {
|
|
o := new(itemOptions)
|
|
for _, optFunc := range opts {
|
|
optFunc(o)
|
|
}
|
|
return &Item[K, V]{
|
|
Key: key,
|
|
Value: val,
|
|
Expiration: o.expiration,
|
|
InitialReferenceCount: o.referenceCount,
|
|
}
|
|
}
|
|
|
|
// Cache is a thread safe cache.
|
|
type Cache[K comparable, V any] struct {
|
|
cache Interface[K, *Item[K, V]]
|
|
// mu is used to do lock in some method process.
|
|
mu sync.Mutex
|
|
janitor *janitor
|
|
expManager *expirationManager[K]
|
|
}
|
|
|
|
// Option is an option for cache.
|
|
type Option[K comparable, V any] func(*options[K, V])
|
|
|
|
type options[K comparable, V any] struct {
|
|
cache Interface[K, *Item[K, V]]
|
|
janitorInterval time.Duration
|
|
}
|
|
|
|
func newOptions[K comparable, V any]() *options[K, V] {
|
|
return &options[K, V]{
|
|
cache: simple.NewCache[K, *Item[K, V]](),
|
|
janitorInterval: time.Minute,
|
|
}
|
|
}
|
|
|
|
// AsLRU is an option to make a new Cache as LRU algorithm.
|
|
func AsLRU[K comparable, V any](opts ...lru.Option) Option[K, V] {
|
|
return func(o *options[K, V]) {
|
|
o.cache = lru.NewCache[K, *Item[K, V]](opts...)
|
|
}
|
|
}
|
|
|
|
// AsLFU is an option to make a new Cache as LFU algorithm.
|
|
func AsLFU[K comparable, V any](opts ...lfu.Option) Option[K, V] {
|
|
return func(o *options[K, V]) {
|
|
o.cache = lfu.NewCache[K, *Item[K, V]](opts...)
|
|
}
|
|
}
|
|
|
|
// AsFIFO is an option to make a new Cache as FIFO algorithm.
|
|
func AsFIFO[K comparable, V any](opts ...fifo.Option) Option[K, V] {
|
|
return func(o *options[K, V]) {
|
|
o.cache = fifo.NewCache[K, *Item[K, V]](opts...)
|
|
}
|
|
}
|
|
|
|
// AsMRU is an option to make a new Cache as MRU algorithm.
|
|
func AsMRU[K comparable, V any](opts ...mru.Option) Option[K, V] {
|
|
return func(o *options[K, V]) {
|
|
o.cache = mru.NewCache[K, *Item[K, V]](opts...)
|
|
}
|
|
}
|
|
|
|
// AsClock is an option to make a new Cache as clock algorithm.
|
|
func AsClock[K comparable, V any](opts ...clock.Option) Option[K, V] {
|
|
return func(o *options[K, V]) {
|
|
o.cache = clock.NewCache[K, *Item[K, V]](opts...)
|
|
}
|
|
}
|
|
|
|
// WithJanitorInterval is an option to specify how often cache should delete expired items.
|
|
//
|
|
// Default is 1 minute.
|
|
func WithJanitorInterval[K comparable, V any](d time.Duration) Option[K, V] {
|
|
return func(o *options[K, V]) {
|
|
o.janitorInterval = d
|
|
}
|
|
}
|
|
|
|
// New creates a new thread safe Cache.
|
|
// The janitor will not be stopped which is created by this function. If you
|
|
// want to stop the janitor gracefully, You should use the `NewContext` function
|
|
// instead of this.
|
|
//
|
|
// There are several Cache replacement policies available with you specified any options.
|
|
func New[K comparable, V any](opts ...Option[K, V]) *Cache[K, V] {
|
|
return NewContext(context.Background(), opts...)
|
|
}
|
|
|
|
// NewContext creates a new thread safe Cache with context.
|
|
// This function will be stopped an internal janitor when the context is cancelled.
|
|
//
|
|
// There are several Cache replacement policies available with you specified any options.
|
|
func NewContext[K comparable, V any](ctx context.Context, opts ...Option[K, V]) *Cache[K, V] {
|
|
o := newOptions[K, V]()
|
|
for _, optFunc := range opts {
|
|
optFunc(o)
|
|
}
|
|
cache := &Cache[K, V]{
|
|
cache: o.cache,
|
|
janitor: newJanitor(ctx, o.janitorInterval),
|
|
expManager: newExpirationManager[K](),
|
|
}
|
|
cache.janitor.run(cache.DeleteExpired)
|
|
return cache
|
|
}
|
|
|
|
// Get looks up a key's value from the cache.
|
|
func (c *Cache[K, V]) Get(key K) (zero V, ok bool) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
item, ok := c.cache.Get(key)
|
|
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Returns nil if the item has been expired.
|
|
// Do not delete here and leave it to an external process such as Janitor.
|
|
if item.Expired() {
|
|
return zero, false
|
|
}
|
|
|
|
return item.Value, true
|
|
}
|
|
|
|
// GetOrSet atomically gets a key's value from the cache, or if the
|
|
// key is not present, sets the given value.
|
|
// The loaded result is true if the value was loaded, false if stored.
|
|
func (c *Cache[K, V]) GetOrSet(key K, val V, opts ...ItemOption) (actual V, loaded bool) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
item, ok := c.cache.Get(key)
|
|
|
|
if !ok || item.Expired() {
|
|
item := newItem(key, val, opts...)
|
|
c.cache.Set(key, item)
|
|
return val, false
|
|
}
|
|
|
|
return item.Value, true
|
|
}
|
|
|
|
// DeleteExpired all expired items from the cache.
|
|
func (c *Cache[K, V]) DeleteExpired() {
|
|
c.mu.Lock()
|
|
l := c.expManager.len()
|
|
c.mu.Unlock()
|
|
|
|
evict := func() bool {
|
|
key := c.expManager.pop()
|
|
// if is expired, delete it and return nil instead
|
|
item, ok := c.cache.Get(key)
|
|
if ok {
|
|
if item.Expired() {
|
|
c.cache.Delete(key)
|
|
return false
|
|
}
|
|
c.expManager.update(key, item.Expiration)
|
|
}
|
|
return true
|
|
}
|
|
|
|
for i := 0; i < l; i++ {
|
|
c.mu.Lock()
|
|
shouldBreak := evict()
|
|
c.mu.Unlock()
|
|
if shouldBreak {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set sets a value to the cache with key. replacing any existing value.
|
|
func (c *Cache[K, V]) Set(key K, val V, opts ...ItemOption) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
item := newItem(key, val, opts...)
|
|
if item.hasExpiration() {
|
|
c.expManager.update(key, item.Expiration)
|
|
}
|
|
c.cache.Set(key, item)
|
|
}
|
|
|
|
// Keys returns the keys of the cache. the order is relied on algorithms.
|
|
func (c *Cache[K, V]) Keys() []K {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
return c.cache.Keys()
|
|
}
|
|
|
|
// Delete deletes the item with provided key from the cache.
|
|
func (c *Cache[K, V]) Delete(key K) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.cache.Delete(key)
|
|
c.expManager.remove(key)
|
|
}
|
|
|
|
// Len returns the number of items in the cache.
|
|
func (c *Cache[K, V]) Len() int {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
return c.cache.Len()
|
|
}
|
|
|
|
// Contains reports whether key is within cache.
|
|
func (c *Cache[K, V]) Contains(key K) bool {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
_, ok := c.cache.Get(key)
|
|
return ok
|
|
}
|
|
|
|
// NumberCache is a in-memory cache which is able to store only Number constraint.
|
|
type NumberCache[K comparable, V Number] struct {
|
|
*Cache[K, V]
|
|
// nmu is used to do lock in Increment/Decrement process.
|
|
// Note that this must be here as a separate mutex because mu in Cache struct is Locked in Get,
|
|
// and if we call mu.Lock in Increment/Decrement, it will cause deadlock.
|
|
nmu sync.Mutex
|
|
}
|
|
|
|
// NewNumber creates a new cache for Number constraint.
|
|
func NewNumber[K comparable, V Number](opts ...Option[K, V]) *NumberCache[K, V] {
|
|
return &NumberCache[K, V]{
|
|
Cache: New(opts...),
|
|
}
|
|
}
|
|
|
|
// Increment an item of type Number constraint by n.
|
|
// Returns the incremented value.
|
|
func (nc *NumberCache[K, V]) Increment(key K, n V) V {
|
|
// In order to avoid lost update, we must lock whole Increment/Decrement process.
|
|
nc.nmu.Lock()
|
|
defer nc.nmu.Unlock()
|
|
got, _ := nc.Cache.Get(key)
|
|
nv := got + n
|
|
nc.Cache.Set(key, nv)
|
|
return nv
|
|
}
|
|
|
|
// Decrement an item of type Number constraint by n.
|
|
// Returns the decremented value.
|
|
func (nc *NumberCache[K, V]) Decrement(key K, n V) V {
|
|
nc.nmu.Lock()
|
|
defer nc.nmu.Unlock()
|
|
got, _ := nc.Cache.Get(key)
|
|
nv := got - n
|
|
nc.Cache.Set(key, nv)
|
|
return nv
|
|
}
|
|
|