honk/vendor/humungus.tedunangst.com/r/webs/cache/cache.go
2022-11-13 16:19:53 +01:00

351 lines
8 KiB
Go

//
// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// A simple in memory, in process cache
package cache
import (
"errors"
"reflect"
"sync"
"time"
"humungus.tedunangst.com/r/webs/gate"
)
// Fill functions should be roughtly compatible with this type.
// They may use stronger types, however.
// It will be called after a cache miss.
// It should return a value and bool indicating success.
type Filler func(key interface{}) (interface{}, bool)
// A function which returns the size of an element in the cache.
// It may be stronger typed.
type Sizer func(res interface{}) int
// A function which reduces a complex key into one suitable for a map.
// It may be stronger typed.
type Reducer func(key interface{}) interface{}
// Arguments to creating a new cache.
// Filler is required. See Filler type documentation.
// Entries will expire after Duration if set.
// Invalidator allows invalidating multiple dependent caches.
// Limit is max entries.
// SizeLimit is max size of all elements, combined with Sizer.
// Reducer allows for the use of complex keys.
type Options struct {
Filler interface{}
Duration time.Duration
Invalidator *Invalidator
Limit int
SizeLimit int
Sizer interface{}
Reducer interface{}
Singleton bool
}
type entry struct {
value interface{}
size int
stale time.Time
}
type entrymap map[interface{}]entry
// The cache object
type Cache struct {
cache entrymap
filler Filler
sizer Sizer
reducer Reducer
lock sync.Mutex
duration time.Duration
serializer *gate.Serializer
serialno int
limit int
size int
sizelimit int
singleton bool
}
// An Invalidator is a collection of caches to be cleared or flushed together.
// It is created, then its address passed to cache creation.
type Invalidator struct {
caches []*Cache
}
// Create a new Cache. Arguments are provided via Options.
func New(options Options) *Cache {
c := new(Cache)
c.cache = make(entrymap)
if fillfn := options.Filler; fillfn != nil {
ftype := reflect.TypeOf(fillfn)
if ftype.Kind() != reflect.Func {
panic("cache filler is not function")
}
if ftype.NumIn() != 1 || ftype.NumOut() != 2 {
panic("cache filler has wrong argument count")
}
vfn := reflect.ValueOf(fillfn)
c.filler = func(key interface{}) (interface{}, bool) {
args := []reflect.Value{reflect.ValueOf(key)}
rv := vfn.Call(args)
return rv[0].Interface(), rv[1].Bool()
}
}
if sizefn := options.Sizer; sizefn != nil {
ftype := reflect.TypeOf(sizefn)
if ftype.Kind() != reflect.Func {
panic("cache sizer is not function")
}
if ftype.NumIn() != 1 || ftype.NumOut() != 1 {
panic("cache sizer has wrong argument count")
}
vfn := reflect.ValueOf(sizefn)
c.sizer = func(res interface{}) int {
args := []reflect.Value{reflect.ValueOf(res)}
rv := vfn.Call(args)
return int(rv[0].Int())
}
}
if reducefn := options.Reducer; reducefn != nil {
ftype := reflect.TypeOf(reducefn)
if ftype.Kind() != reflect.Func {
panic("cache sizer is not function")
}
if ftype.NumIn() != 1 || ftype.NumOut() != 1 {
panic("cache sizer has wrong argument count")
}
vfn := reflect.ValueOf(reducefn)
c.reducer = func(res interface{}) interface{} {
args := []reflect.Value{reflect.ValueOf(res)}
rv := vfn.Call(args)
return rv[0].Interface()
}
}
if options.Duration != 0 {
c.duration = options.Duration
}
if options.Invalidator != nil {
options.Invalidator.caches = append(options.Invalidator.caches, c)
}
c.serializer = gate.NewSerializer()
c.limit = options.Limit
c.sizelimit = options.SizeLimit
c.singleton = options.Singleton
return c
}
// Get a value for a key. Returns true for success.
// Will automatically fill the cache.
// Returns holding the cache lock. Useful when the cached value can mutate.
func (c *Cache) GetAndLock(key interface{}, value interface{}) bool {
origkey := key
if c.reducer != nil {
key = c.reducer(key)
}
c.lock.Lock()
recheck:
ent, ok := c.cache[key]
if ok {
if !ent.stale.IsZero() && ent.stale.Before(time.Now()) {
c.remove(key, ent)
ok = false
}
}
if !ok {
if c.filler == nil {
return false
}
serial := c.serialno
c.lock.Unlock()
r, err := c.serializer.Call(key, func() (interface{}, error) {
v, ok := c.filler(origkey)
if !ok {
return nil, errors.New("no fill")
}
return v, nil
})
c.lock.Lock()
if err == gate.Cancelled || serial != c.serialno {
goto recheck
}
if err == nil {
c.set(key, r)
ent.value, ok = r, true
}
}
if ok {
ptr := reflect.ValueOf(ent.value)
reflect.ValueOf(value).Elem().Set(ptr)
}
return ok
}
// Get a value for a key. Returns true for success.
// Will automatically fill the cache.
func (c *Cache) Get(key interface{}, value interface{}) bool {
rv := c.GetAndLock(key, value)
c.lock.Unlock()
return rv
}
func (c *Cache) set(key interface{}, value interface{}) {
var stale time.Time
if c.duration != 0 {
stale = time.Now().Add(c.duration)
}
size := 0
if c.sizer != nil {
size = c.sizer(value)
}
if c.limit > 0 && len(c.cache) >= c.limit {
tries := 0
var now time.Time
if c.duration != 0 {
now = time.Now()
} else {
tries = 5
}
for key, ent := range c.cache {
if tries < 5 && ent.stale.After(now) {
tries++
continue
}
c.remove(key, ent)
break
}
}
if c.sizelimit > 0 {
if size > c.sizelimit/4 {
return
}
if size+c.size > c.sizelimit {
for key, ent := range c.cache {
c.remove(key, ent)
if size+c.size <= c.sizelimit {
break
}
}
}
}
c.size += size
c.cache[key] = entry{
value: value,
stale: stale,
size: size,
}
}
func (c *Cache) remove(key interface{}, ent entry) {
c.size -= ent.size
delete(c.cache, key)
}
// Manually set a cached value.
func (c *Cache) Set(key interface{}, value interface{}) {
if c.reducer != nil {
key = c.reducer(key)
}
c.lock.Lock()
c.set(key, value)
c.lock.Unlock()
}
// Unlock the c, iff lock is held.
func (c *Cache) Unlock() {
c.lock.Unlock()
}
// Clear one key from the cache
func (c *Cache) Clear(key interface{}) {
if c.singleton {
c.Flush()
return
}
if c.reducer != nil {
key = c.reducer(key)
}
c.lock.Lock()
if ent, ok := c.cache[key]; ok {
c.serialno++
c.remove(key, ent)
}
c.serializer.Cancel(key)
c.lock.Unlock()
}
// Flush all values from the cache
func (c *Cache) Flush() {
c.lock.Lock()
c.serialno++
c.cache = make(entrymap)
c.serializer.CancelAll()
c.lock.Unlock()
}
// Clear one key from associated caches
func (inv Invalidator) Clear(key interface{}) {
for _, c := range inv.caches {
c.Clear(key)
}
}
// Flush all values from associated caches
func (inv Invalidator) Flush() {
for _, c := range inv.caches {
c.Flush()
}
}
type Counter struct {
cache *Cache
}
func NewCounter(options Options) Counter {
var c Counter
c.cache = New(Options{Filler: func(name string) (int64, bool) {
return 0, true
}, Duration: options.Duration, Limit: options.Limit})
return c
}
func (cnt Counter) Get(name string) int64 {
c := cnt.cache
var val int64
c.Get(name, &val)
return val
}
func (cnt Counter) Inc(name string) int64 {
c := cnt.cache
var val int64
c.GetAndLock(name, &val)
val++
c.set(name, val)
c.Unlock()
return val
}
func (cnt Counter) Dec(name string) int64 {
c := cnt.cache
var val int64
c.GetAndLock(name, &val)
val--
c.set(name, val)
c.Unlock()
return val
}