Skip to content

Commit

Permalink
Remove list implementation from record
Browse files Browse the repository at this point in the history
  • Loading branch information
mgnsk committed Sep 4, 2023
1 parent 5fb8262 commit 04f922d
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 104 deletions.
76 changes: 40 additions & 36 deletions backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
}
}
Expand All @@ -39,66 +43,66 @@ 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)

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
})
b.mu.RUnlock()

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)
}
}
Expand All @@ -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
Expand Down
44 changes: 23 additions & 21 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"runtime"
"sync"
"time"

"github.com/mgnsk/list"
)

// Cache is an in-memory TTL cache with optional capacity.
Expand Down Expand Up @@ -32,17 +34,17 @@ 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.
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
Expand All @@ -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
})
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
44 changes: 0 additions & 44 deletions record.go

This file was deleted.

0 comments on commit 04f922d

Please sign in to comment.