From 04f922d0d9ee4e08df42c3e1788df32886e7ba2c Mon Sep 17 00:00:00 2001 From: Magnus Kokk Date: Mon, 4 Sep 2023 19:52:44 +0300 Subject: [PATCH] Remove list implementation from record --- backend.go | 76 ++++++++++++++++++++++++++++-------------------------- cache.go | 44 ++++++++++++++++--------------- go.mod | 2 +- go.sum | 4 +-- record.go | 44 ------------------------------- 5 files changed, 66 insertions(+), 104 deletions(-) delete mode 100644 record.go diff --git a/backend.go b/backend.go index 57aa1fc..9dda38e 100644 --- a/backend.go +++ b/backend.go @@ -2,20 +2,24 @@ package evcache import ( "sync" + "sync/atomic" "time" "github.com/mgnsk/list" ) -type recordList[V any] struct { - list.ListOf[record[V], *record[V]] +type record[V any] struct { + value V + deadline int64 + wg sync.WaitGroup + initialized atomic.Bool } type backend[K comparable, V any] struct { timer *time.Timer done chan struct{} - xmap backendMap[K, *record[V]] - list recordList[V] + xmap backendMap[K, *list.Element[record[V]]] + list list.List[record[V]] earliestExpireAt int64 cap int once sync.Once @@ -29,7 +33,7 @@ func newBackend[K comparable, V any](capacity int) *backend[K, V] { return &backend[K, V]{ timer: t, done: make(chan struct{}), - xmap: make(builtinMap[K, *record[V]], capacity), + xmap: make(builtinMap[K, *list.Element[record[V]]], capacity), cap: capacity, } } @@ -39,19 +43,19 @@ func (b *backend[K, V]) close() error { return nil } -func (b *backend[K, V]) LoadOrStore(key K, new *record[V]) (old *record[V], loaded bool) { +func (b *backend[K, V]) LoadOrStore(key K, new *list.Element[record[V]]) (old *list.Element[record[V]], loaded bool) { b.mu.RLock() - if r, ok := b.xmap.Load(key); ok { + if elem, ok := b.xmap.Load(key); ok { b.mu.RUnlock() - return r, true + return elem, true } b.mu.RUnlock() b.mu.Lock() defer b.mu.Unlock() - if r, ok := b.xmap.Load(key); ok { - return r, true + if elem, ok := b.xmap.Load(key); ok { + return elem, true } b.xmap.Store(key, new) @@ -59,10 +63,10 @@ func (b *backend[K, V]) LoadOrStore(key K, new *record[V]) (old *record[V], load return new, false } -func (b *backend[K, V]) Range(f func(key K, r *record[V]) bool) { +func (b *backend[K, V]) Range(f func(key K, r *list.Element[record[V]]) bool) { b.mu.RLock() keys := make([]K, 0, b.xmap.Len()) - b.xmap.Range(func(key K, _ *record[V]) bool { + b.xmap.Range(func(key K, _ *list.Element[record[V]]) bool { keys = append(keys, key) return true }) @@ -70,35 +74,35 @@ func (b *backend[K, V]) Range(f func(key K, r *record[V]) bool) { for _, key := range keys { b.mu.RLock() - r, ok := b.xmap.Load(key) + elem, ok := b.xmap.Load(key) b.mu.RUnlock() - if ok && !f(key, r) { + if ok && !f(key, elem) { return } } } -func (b *backend[K, V]) evict(key K) (*record[V], bool) { - if r, ok := b.xmap.Load(key); ok && r.initialized.Load() { +func (b *backend[K, V]) evict(key K) (*list.Element[record[V]], bool) { + if elem, ok := b.xmap.Load(key); ok && elem.Value.initialized.Load() { b.xmap.Delete(key) - b.list.Remove(r) - return r, true + b.list.Remove(elem) + return elem, true } return nil, false } -func (b *backend[K, V]) pushBack(r *record[V], ttl time.Duration) { - b.list.PushBack(r) - r.initialized.Store(true) +func (b *backend[K, V]) pushBack(elem *list.Element[record[V]], ttl time.Duration) { + b.list.PushBack(elem) + elem.Value.initialized.Store(true) if n := b.overflow(); n > 0 { b.startGCOnce() b.timer.Reset(0) - } else if r.deadline > 0 { + } else if elem.Value.deadline > 0 { b.startGCOnce() - if b.earliestExpireAt == 0 || r.deadline < b.earliestExpireAt { - b.earliestExpireAt = r.deadline + if b.earliestExpireAt == 0 || elem.Value.deadline < b.earliestExpireAt { + b.earliestExpireAt = elem.Value.deadline b.timer.Reset(ttl) } } @@ -124,29 +128,29 @@ func (b *backend[K, V]) runGC(now int64) { b.mu.Lock() defer b.mu.Unlock() - var overflowed map[*record[V]]bool + var overflowed map[*list.Element[record[V]]]bool if n := b.overflow(); n > 0 { - overflowed = make(map[*record[V]]bool, n) + overflowed = make(map[*list.Element[record[V]]]bool, n) - r := b.list.Front() - overflowed[r] = true + elem := b.list.Front() + overflowed[elem] = true for i := 1; i < n; i++ { - r = r.Next() - overflowed[r] = true + elem = elem.Next() + overflowed[elem] = true } } var earliest int64 - b.xmap.Range(func(key K, r *record[V]) bool { - if r.initialized.Load() { - if len(overflowed) > 0 && overflowed[r] || r.deadline > 0 && r.deadline < now { + b.xmap.Range(func(key K, elem *list.Element[record[V]]) bool { + if elem.Value.initialized.Load() { + if len(overflowed) > 0 && overflowed[elem] || elem.Value.deadline > 0 && elem.Value.deadline < now { b.xmap.Delete(key) - b.list.Remove(r) - } else if r.deadline > 0 && (earliest == 0 || r.deadline < earliest) { - earliest = r.deadline + b.list.Remove(elem) + } else if elem.Value.deadline > 0 && (earliest == 0 || elem.Value.deadline < earliest) { + earliest = elem.Value.deadline } } return true diff --git a/cache.go b/cache.go index 5ce744d..6dbceaf 100644 --- a/cache.go +++ b/cache.go @@ -4,6 +4,8 @@ import ( "runtime" "sync" "time" + + "github.com/mgnsk/list" ) // Cache is an in-memory TTL cache with optional capacity. @@ -32,8 +34,8 @@ func (c *Cache[K, V]) Exists(key K) bool { c.backend.mu.RLock() defer c.backend.mu.RUnlock() - r, ok := c.backend.xmap.Load(key) - return ok && r.initialized.Load() + elem, ok := c.backend.xmap.Load(key) + return ok && elem.Value.initialized.Load() } // Get returns the value stored in the cache for key. @@ -41,8 +43,8 @@ func (c *Cache[K, V]) Get(key K) (value V, exists bool) { c.backend.mu.RLock() defer c.backend.mu.RUnlock() - if r, ok := c.backend.xmap.Load(key); ok && r.initialized.Load() { - return r.value, true + if elem, ok := c.backend.xmap.Load(key); ok && elem.Value.initialized.Load() { + return elem.Value.value, true } var zero V @@ -54,9 +56,9 @@ func (c *Cache[K, V]) Get(key K) (value V, exists bool) { // // Range is allowed to modify the cache. func (c *Cache[K, V]) Range(f func(key K, value V) bool) { - c.backend.Range(func(key K, r *record[V]) bool { - if r.initialized.Load() { - return f(key, r.value) + c.backend.Range(func(key K, elem *list.Element[record[V]]) bool { + if elem.Value.initialized.Load() { + return f(key, elem.Value.value) } return true }) @@ -75,8 +77,8 @@ func (c *Cache[K, V]) Evict(key K) (value V, ok bool) { c.backend.mu.Lock() defer c.backend.mu.Unlock() - if r, ok := c.backend.evict(key); ok { - return r.value, true + if elem, ok := c.backend.evict(key); ok { + return elem.Value.value, true } var zero V @@ -117,26 +119,26 @@ func (c *Cache[K, V]) Fetch(key K, ttl time.Duration, f func() (V, error)) (valu // TryFetch is like Fetch but allows the TTL to be returned alongside the value from callback. func (c *Cache[K, V]) TryFetch(key K, f func() (V, time.Duration, error)) (value V, err error) { - new, ok := c.pool.Get().(*record[V]) + new, ok := c.pool.Get().(*list.Element[record[V]]) if !ok { - new = newRecord[V]() + new = list.NewElement(record[V]{}) } - new.wg.Add(1) - defer new.wg.Done() + new.Value.wg.Add(1) + defer new.Value.wg.Done() loadOrStore: - if r, loaded := c.backend.LoadOrStore(key, new); loaded { - if r.initialized.Load() { + if elem, loaded := c.backend.LoadOrStore(key, new); loaded { + if elem.Value.initialized.Load() { c.pool.Put(new) - return r.value, nil + return elem.Value.value, nil } - r.wg.Wait() + elem.Value.wg.Wait() - if r.initialized.Load() { + if elem.Value.initialized.Load() { c.pool.Put(new) - return r.value, nil + return elem.Value.value, nil } goto loadOrStore @@ -164,9 +166,9 @@ loadOrStore: return zero, err } - new.value = value + new.Value.value = value if ttl > 0 { - new.deadline = time.Now().Add(ttl).UnixNano() + new.Value.deadline = time.Now().Add(ttl).UnixNano() } c.backend.mu.Lock() diff --git a/go.mod b/go.mod index 5fea14a..1fede0f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/mgnsk/evcache/v3 go 1.19 require ( - github.com/mgnsk/list v0.0.0-20230828152933-f6729dafd361 + github.com/mgnsk/list v0.0.0-20230904165030-d976a4f02c5c github.com/onsi/gomega v1.27.10 ) diff --git a/go.sum b/go.sum index 1b48f09..adc1006 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mgnsk/list v0.0.0-20230828152933-f6729dafd361 h1:JBd03hTO20OfizYtUXPTVnyM5NFGRqiJc6YABtnPcuE= -github.com/mgnsk/list v0.0.0-20230828152933-f6729dafd361/go.mod h1:NSeUCVwmqTJGeKJW1DFe0En63T065/tLyj5HPwwBZd0= +github.com/mgnsk/list v0.0.0-20230904165030-d976a4f02c5c h1:5aNAw0KUsCf4tO2SAoY06TZWkUoWN+Ap7WfoI+OAxbg= +github.com/mgnsk/list v0.0.0-20230904165030-d976a4f02c5c/go.mod h1:NSeUCVwmqTJGeKJW1DFe0En63T065/tLyj5HPwwBZd0= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= diff --git a/record.go b/record.go deleted file mode 100644 index 55a9513..0000000 --- a/record.go +++ /dev/null @@ -1,44 +0,0 @@ -package evcache - -import ( - "sync" - "sync/atomic" -) - -type record[V any] struct { - value V - next, prev *record[V] - deadline int64 - wg sync.WaitGroup - initialized atomic.Bool -} - -func newRecord[V any]() *record[V] { - r := &record[V]{} - r.next = r - r.prev = r - return r -} - -func (r *record[V]) Next() *record[V] { - return r.next -} - -func (r *record[V]) Prev() *record[V] { - return r.prev -} - -func (r *record[V]) Link(s *record[V]) { - n := r.next - r.next = s - s.prev = r - n.prev = s - s.next = n -} - -func (r *record[V]) Unlink() { - r.prev.next = r.next - r.next.prev = r.prev - r.next = r - r.prev = r -}