wip
This commit is contained in:
134
kvmemcache/cache.go
Normal file
134
kvmemcache/cache.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package kvmemcache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.crumpington.com/lib/keyedmutex"
|
||||
)
|
||||
|
||||
type Cache[K comparable, V any] struct {
|
||||
updateLock keyedmutex.KeyedMutex[K]
|
||||
src func(K) (V, error)
|
||||
ttl time.Duration
|
||||
maxSize int
|
||||
|
||||
// Lock protects variables below.
|
||||
lock sync.Mutex
|
||||
cache map[K]*list.Element
|
||||
ll *list.List
|
||||
stats Stats
|
||||
}
|
||||
|
||||
type lruItem[K comparable, V any] struct {
|
||||
key K
|
||||
createdAt time.Time
|
||||
value V
|
||||
err error
|
||||
}
|
||||
|
||||
type Config[K comparable, V any] struct {
|
||||
MaxSize int
|
||||
TTL time.Duration // Zero to ignore.
|
||||
Src func(K) (V, error)
|
||||
}
|
||||
|
||||
func New[K comparable, V any](conf Config[K, V]) *Cache[K, V] {
|
||||
return &Cache[K, V]{
|
||||
updateLock: keyedmutex.New[K](),
|
||||
src: conf.Src,
|
||||
ttl: conf.TTL,
|
||||
maxSize: conf.MaxSize,
|
||||
lock: sync.Mutex{},
|
||||
cache: make(map[K]*list.Element, conf.MaxSize+1),
|
||||
ll: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) Get(key K) (V, error) {
|
||||
ok, val, err := c.get(key)
|
||||
if ok {
|
||||
return val, err
|
||||
}
|
||||
|
||||
return c.load(key)
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) Evict(key K) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.evict(key)
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) Stats() Stats {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
return c.stats
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) put(key K, value V, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.stats.Misses++
|
||||
|
||||
c.cache[key] = c.ll.PushFront(lruItem[K, V]{
|
||||
key: key,
|
||||
createdAt: time.Now(),
|
||||
value: value,
|
||||
err: err,
|
||||
})
|
||||
|
||||
if c.maxSize != 0 && len(c.cache) > c.maxSize {
|
||||
li := c.ll.Back()
|
||||
c.ll.Remove(li)
|
||||
delete(c.cache, li.Value.(lruItem[K, V]).key)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) evict(key K) {
|
||||
elem := c.cache[key]
|
||||
if elem != nil {
|
||||
delete(c.cache, key)
|
||||
c.ll.Remove(elem)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) get(key K) (ok bool, val V, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
li := c.cache[key]
|
||||
if li == nil {
|
||||
return false, val, nil
|
||||
}
|
||||
|
||||
item := li.Value.(lruItem[K, V])
|
||||
// Maybe evict.
|
||||
if c.ttl != 0 && time.Since(item.createdAt) > c.ttl {
|
||||
c.evict(key)
|
||||
return false, val, nil
|
||||
}
|
||||
|
||||
c.stats.Hits++
|
||||
|
||||
c.ll.MoveToFront(li)
|
||||
return true, item.value, item.err
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) load(key K) (V, error) {
|
||||
c.updateLock.Lock(key)
|
||||
defer c.updateLock.Unlock(key)
|
||||
|
||||
// Check again in case we lost the update race.
|
||||
ok, val, err := c.get(key)
|
||||
if ok {
|
||||
return val, err
|
||||
}
|
||||
|
||||
// Won the update race.
|
||||
val, err = c.src(key)
|
||||
c.put(key, val, err)
|
||||
return val, err
|
||||
}
|
||||
Reference in New Issue
Block a user