diff --git a/pkg/localcache/local/cache.go b/pkg/localcache/local/cache.go index 06569965e..e21d72425 100644 --- a/pkg/localcache/local/cache.go +++ b/pkg/localcache/local/cache.go @@ -14,18 +14,18 @@ type Cache[V any] interface { func NewCache[V any](slotNum, slotSize int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[string, V]) Cache[V] { c := &slot[V]{ n: uint64(slotNum), - slots: make([]*LRU[string, V], slotNum), + slots: make([]*InertiaLRU[string, V], slotNum), target: target, } for i := 0; i < slotNum; i++ { - c.slots[i] = NewLRU[string, V](slotSize, successTTL, failedTTL, c.target, onEvict) + c.slots[i] = NewInertiaLRU[string, V](slotSize, successTTL, failedTTL, c.target, onEvict) } return c } type slot[V any] struct { n uint64 - slots []*LRU[string, V] + slots []*InertiaLRU[string, V] target Target } diff --git a/pkg/localcache/local/callback.go b/pkg/localcache/local/callback.go index 32aef112b..469c3dc0d 100644 --- a/pkg/localcache/local/callback.go +++ b/pkg/localcache/local/callback.go @@ -1,5 +1 @@ package local - -import "github.com/hashicorp/golang-lru/v2/simplelru" - -type EvictCallback[K comparable, V any] simplelru.EvictCallback[K, V] diff --git a/pkg/localcache/local/lru.go b/pkg/localcache/local/lru.go index 5ce0f1cc4..9cb984b81 100644 --- a/pkg/localcache/local/lru.go +++ b/pkg/localcache/local/lru.go @@ -1,108 +1,20 @@ package local -import ( - "github.com/hashicorp/golang-lru/v2/simplelru" - "sync" - "time" -) +import "github.com/hashicorp/golang-lru/v2/simplelru" -type LRU1[K comparable, V any] interface { +type EvictCallback[K comparable, V any] simplelru.EvictCallback[K, V] + +type LRU[K comparable, V any] interface { Get(key K, fetch func() (V, error)) (V, error) Del(key K) bool Stop() } -//type expirableLRU[K comparable, V any] struct { -// core expirable.LRU[K, V] -//} -// -//func (x *expirableLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) { -// -// return x.core.Get(key, fetch) -//} -// -//func (x *expirableLRU[K, V]) Del(key K) bool { -// return x.core.Remove(key) -//} -// -//func (x *expirableLRU[K, V]) Stop() { -// -//} - -type waitItem[V any] struct { - lock sync.Mutex - expires int64 - err error - value V -} - -func NewLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) *LRU[K, V] { - var cb simplelru.EvictCallback[K, *waitItem[V]] - if onEvict != nil { - cb = func(key K, value *waitItem[V]) { - onEvict(key, value.value) - } - } - core, err := simplelru.NewLRU[K, *waitItem[V]](size, cb) - if err != nil { - panic(err) - } - return &LRU[K, V]{ - core: core, - successTTL: successTTL, - failedTTL: failedTTL, - target: target, - } -} - -type LRU[K comparable, V any] struct { - lock sync.Mutex - core *simplelru.LRU[K, *waitItem[V]] - successTTL time.Duration - failedTTL time.Duration - target Target -} - -func (x *LRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) { - x.lock.Lock() - v, ok := x.core.Get(key) - if ok { - x.lock.Unlock() - v.lock.Lock() - expires, value, err := v.expires, v.value, v.err - if expires != 0 && expires > time.Now().UnixMilli() { - v.lock.Unlock() - x.target.IncrGetHit() - return value, err - } - } else { - v = &waitItem[V]{} - x.core.Add(key, v) - v.lock.Lock() - x.lock.Unlock() - } - defer v.lock.Unlock() - if v.expires > time.Now().UnixMilli() { - return v.value, v.err - } - v.value, v.err = fetch() - if v.err == nil { - v.expires = time.Now().Add(x.successTTL).UnixMilli() - x.target.IncrGetSuccess() - } else { - v.expires = time.Now().Add(x.failedTTL).UnixMilli() - x.target.IncrGetFailed() - } - return v.value, v.err -} - -func (x *LRU[K, V]) Del(key K) bool { - x.lock.Lock() - ok := x.core.Remove(key) - x.lock.Unlock() - return ok -} - -func (x *LRU[K, V]) Stop() { +type Target interface { + IncrGetHit() + IncrGetSuccess() + IncrGetFailed() + IncrDelHit() + IncrDelNotFound() } diff --git a/pkg/localcache/local/lru_expirable.go b/pkg/localcache/local/lru_expirable.go new file mode 100644 index 000000000..72acb4ffa --- /dev/null +++ b/pkg/localcache/local/lru_expirable.go @@ -0,0 +1,73 @@ +package local + +import ( + "github.com/hashicorp/golang-lru/v2/expirable" + "sync" + "time" +) + +func NewExpirableLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) LRU[K, V] { + var cb expirable.EvictCallback[K, *expirableLruItem[V]] + if onEvict != nil { + cb = func(key K, value *expirableLruItem[V]) { + onEvict(key, value.value) + } + } + core := expirable.NewLRU[K, *expirableLruItem[V]](size, cb, successTTL) + return &expirableLRU[K, V]{ + core: core, + successTTL: successTTL, + failedTTL: failedTTL, + target: target, + } +} + +type expirableLruItem[V any] struct { + lock sync.RWMutex + err error + value V +} + +type expirableLRU[K comparable, V any] struct { + lock sync.Mutex + core *expirable.LRU[K, *expirableLruItem[V]] + successTTL time.Duration + failedTTL time.Duration + target Target +} + +func (x *expirableLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) { + x.lock.Lock() + v, ok := x.core.Get(key) + if ok { + x.lock.Unlock() + x.target.IncrGetSuccess() + v.lock.RLock() + defer v.lock.RUnlock() + return v.value, v.err + } else { + v = &expirableLruItem[V]{} + x.core.Add(key, v) + v.lock.Lock() + x.lock.Unlock() + defer v.lock.Unlock() + v.value, v.err = fetch() + if v.err == nil { + x.target.IncrGetSuccess() + } else { + x.target.IncrGetFailed() + x.core.Remove(key) + } + return v.value, v.err + } +} + +func (x *expirableLRU[K, V]) Del(key K) bool { + x.lock.Lock() + ok := x.core.Remove(key) + x.lock.Unlock() + return ok +} + +func (x *expirableLRU[K, V]) Stop() { +} diff --git a/pkg/localcache/local/lru_inertia.go b/pkg/localcache/local/lru_inertia.go new file mode 100644 index 000000000..cd161e16e --- /dev/null +++ b/pkg/localcache/local/lru_inertia.go @@ -0,0 +1,85 @@ +package local + +import ( + "github.com/hashicorp/golang-lru/v2/simplelru" + "sync" + "time" +) + +type inertiaLruItem[V any] struct { + lock sync.Mutex + expires int64 + err error + value V +} + +func NewInertiaLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) *InertiaLRU[K, V] { + var cb simplelru.EvictCallback[K, *inertiaLruItem[V]] + if onEvict != nil { + cb = func(key K, value *inertiaLruItem[V]) { + onEvict(key, value.value) + } + } + core, err := simplelru.NewLRU[K, *inertiaLruItem[V]](size, cb) + if err != nil { + panic(err) + } + return &InertiaLRU[K, V]{ + core: core, + successTTL: successTTL, + failedTTL: failedTTL, + target: target, + } +} + +type InertiaLRU[K comparable, V any] struct { + lock sync.Mutex + core *simplelru.LRU[K, *inertiaLruItem[V]] + successTTL time.Duration + failedTTL time.Duration + target Target +} + +func (x *InertiaLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) { + x.lock.Lock() + v, ok := x.core.Get(key) + if ok { + x.lock.Unlock() + v.lock.Lock() + expires, value, err := v.expires, v.value, v.err + if expires != 0 && expires > time.Now().UnixMilli() { + v.lock.Unlock() + x.target.IncrGetHit() + return value, err + } + } else { + v = &inertiaLruItem[V]{} + x.core.Add(key, v) + v.lock.Lock() + x.lock.Unlock() + } + defer v.lock.Unlock() + if v.expires > time.Now().UnixMilli() { + return v.value, v.err + } + v.value, v.err = fetch() + if v.err == nil { + v.expires = time.Now().Add(x.successTTL).UnixMilli() + x.target.IncrGetSuccess() + } else { + v.expires = time.Now().Add(x.failedTTL).UnixMilli() + x.target.IncrGetFailed() + } + return v.value, v.err +} + +func (x *InertiaLRU[K, V]) Del(key K) bool { + x.lock.Lock() + ok := x.core.Remove(key) + x.lock.Unlock() + return ok +} + +func (x *InertiaLRU[K, V]) Stop() { + +} diff --git a/pkg/localcache/local/lru_test.go b/pkg/localcache/local/lru_inertia_test.go similarity index 95% rename from pkg/localcache/local/lru_test.go rename to pkg/localcache/local/lru_inertia_test.go index a6e7553ee..6bff90011 100644 --- a/pkg/localcache/local/lru_test.go +++ b/pkg/localcache/local/lru_inertia_test.go @@ -43,7 +43,7 @@ func (r *cacheTarget) String() string { func TestName(t *testing.T) { target := &cacheTarget{} l := NewCache[string](100, 1000, time.Second*20, time.Second*5, target, nil) - //l := NewLRU[string, string](1000, time.Second*20, time.Second*5, target) + //l := NewInertiaLRU[string, string](1000, time.Second*20, time.Second*5, target) fn := func(key string, n int, fetch func() (string, error)) { for i := 0; i < n; i++ { diff --git a/pkg/localcache/local/target.go b/pkg/localcache/local/target.go deleted file mode 100644 index 6cb134fb0..000000000 --- a/pkg/localcache/local/target.go +++ /dev/null @@ -1,10 +0,0 @@ -package local - -type Target interface { - IncrGetHit() - IncrGetSuccess() - IncrGetFailed() - - IncrDelHit() - IncrDelNotFound() -}