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

View File

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

View File

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