This commit is contained in:
Mike Schwörer 2022-12-22 10:06:25 +01:00
parent d4994b8c8d
commit 1aaad66233
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
3 changed files with 32 additions and 42 deletions

View File

@ -19,40 +19,38 @@ import (
// There are also a bunch of unit tests to ensure that the cache is always in a consistent state
//
type LRUData interface{}
type LRUMap struct {
type LRUMap[TData any] struct {
maxsize int
lock sync.Mutex
cache map[string]*cacheNode
cache map[string]*cacheNode[TData]
lfuHead *cacheNode
lfuTail *cacheNode
lfuHead *cacheNode[TData]
lfuTail *cacheNode[TData]
}
type cacheNode struct {
type cacheNode[TData any] struct {
key string
data LRUData
parent *cacheNode
child *cacheNode
data TData
parent *cacheNode[TData]
child *cacheNode[TData]
}
func NewLRUMap(size int) *LRUMap {
func NewLRUMap[TData any](size int) *LRUMap[TData] {
if size <= 2 && size != 0 {
panic("Size must be > 2 (or 0)")
}
return &LRUMap{
return &LRUMap[TData]{
maxsize: size,
lock: sync.Mutex{},
cache: make(map[string]*cacheNode, size+1),
cache: make(map[string]*cacheNode[TData], size+1),
lfuHead: nil,
lfuTail: nil,
}
}
func (c *LRUMap) Put(key string, value LRUData) {
func (c *LRUMap[TData]) Put(key string, value TData) {
if c.maxsize == 0 {
return // cache disabled
}
@ -70,7 +68,7 @@ func (c *LRUMap) Put(key string, value LRUData) {
}
// key does not exist: insert into map and add to top of LFU
node = &cacheNode{
node = &cacheNode[TData]{
key: key,
data: value,
parent: nil,
@ -95,9 +93,9 @@ func (c *LRUMap) Put(key string, value LRUData) {
}
}
func (c *LRUMap) TryGet(key string) (LRUData, bool) {
func (c *LRUMap[TData]) TryGet(key string) (TData, bool) {
if c.maxsize == 0 {
return nil, false // cache disabled
return *new(TData), false // cache disabled
}
c.lock.Lock()
@ -105,13 +103,13 @@ func (c *LRUMap) TryGet(key string) (LRUData, bool) {
val, ok := c.cache[key]
if !ok {
return nil, false
return *new(TData), false
}
c.moveNodeToTop(val)
return val.data, ok
}
func (c *LRUMap) moveNodeToTop(node *cacheNode) {
func (c *LRUMap[TData]) moveNodeToTop(node *cacheNode[TData]) {
// (only called in critical section !)
if c.lfuHead == node { // fast case
@ -144,7 +142,7 @@ func (c *LRUMap) moveNodeToTop(node *cacheNode) {
}
}
func (c *LRUMap) Size() int {
func (c *LRUMap[TData]) Size() int {
c.lock.Lock()
defer c.lock.Unlock()
return len(c.cache)

View File

@ -12,7 +12,7 @@ func init() {
}
func TestResultCache1(t *testing.T) {
cache := NewLRUMap(8)
cache := NewLRUMap[string](8)
verifyLRUList(cache, t)
key := randomKey()
@ -39,7 +39,7 @@ func TestResultCache1(t *testing.T) {
if !ok {
t.Errorf("cache TryGet returned no value")
}
if !eq(cacheval, val) {
if cacheval != val {
t.Errorf("cache TryGet returned different value (%+v <> %+v)", cacheval, val)
}
@ -50,7 +50,7 @@ func TestResultCache1(t *testing.T) {
}
func TestResultCache2(t *testing.T) {
cache := NewLRUMap(8)
cache := NewLRUMap[string](8)
verifyLRUList(cache, t)
key1 := "key1"
@ -150,7 +150,7 @@ func TestResultCache2(t *testing.T) {
}
func TestResultCache3(t *testing.T) {
cache := NewLRUMap(8)
cache := NewLRUMap[string](8)
verifyLRUList(cache, t)
key1 := "key1"
@ -160,20 +160,20 @@ func TestResultCache3(t *testing.T) {
cache.Put(key1, val1)
verifyLRUList(cache, t)
if val, ok := cache.TryGet(key1); !ok || !eq(val, val1) {
if val, ok := cache.TryGet(key1); !ok || val != val1 {
t.Errorf("Value in cache should be [val1]")
}
cache.Put(key1, val2)
verifyLRUList(cache, t)
if val, ok := cache.TryGet(key1); !ok || !eq(val, val2) {
if val, ok := cache.TryGet(key1); !ok || val != val2 {
t.Errorf("Value in cache should be [val2]")
}
}
// does a basic consistency check over the internal cache representation
func verifyLRUList(cache *LRUMap, t *testing.T) {
func verifyLRUList[TData any](cache *LRUMap[TData], t *testing.T) {
size := 0
tailFound := false
@ -250,23 +250,10 @@ func randomKey() string {
return strconv.FormatInt(rand.Int63(), 16)
}
func randomVal() LRUData {
func randomVal() string {
v, err := langext.NewHexUUID()
if err != nil {
panic(err)
}
return &v
}
func eq(a LRUData, b LRUData) bool {
v1, ok1 := a.(*string)
v2, ok2 := b.(*string)
if ok1 && ok2 {
if v1 == nil || v2 == nil {
return false
}
return v1 == v2
}
return false
return v
}

View File

@ -13,6 +13,7 @@ type DB interface {
Ping(ctx context.Context) error
BeginTransaction(ctx context.Context, iso sql.IsolationLevel) (Tx, error)
AddListener(listener Listener)
Exit() error
}
type database struct {
@ -121,3 +122,7 @@ func (db *database) BeginTransaction(ctx context.Context, iso sql.IsolationLevel
return NewTransaction(xtx, txid, db.lstr), nil
}
func (db *database) Exit() error {
return db.db.Close()
}