Skip to content

Commit

Permalink
cache: Add Map (#60)
Browse files Browse the repository at this point in the history
* cache: Add Map

* cache: Fix test
  • Loading branch information
sunshineplan authored Jun 19, 2024
1 parent dcca62f commit 56f0550
Show file tree
Hide file tree
Showing 5 changed files with 617 additions and 38 deletions.
93 changes: 56 additions & 37 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,6 @@ import (

var valueKey int

func newContext(value any, lifecycle time.Duration) (ctx context.Context, cancel context.CancelFunc) {
ctx = context.WithValue(context.Background(), &valueKey, value)
if lifecycle > 0 {
ctx, cancel = context.WithTimeout(ctx, lifecycle)
}
return
}

type item[T any] struct {
sync.Mutex
context.Context
Expand All @@ -25,27 +17,31 @@ type item[T any] struct {
fn func() (T, error)
}

func (i *item[T]) set(value T) {
if i.Context = context.WithValue(context.Background(), &valueKey, value); i.lifecycle > 0 {
i.Context, i.cancel = context.WithTimeout(i.Context, i.lifecycle)
}
}

func (i *item[T]) value() T {
i.Lock()
defer i.Unlock()
return i.Value(&valueKey).(T)
}

func (i *item[T]) renew() T {
v, err := i.fn()
i.Lock()
defer i.Unlock()
if err != nil {
log.Print(err)
v = i.value()
}
i.Lock()
defer i.Unlock()
i.Context, i.cancel = newContext(v, i.lifecycle)
i.set(v)
return v
}

// Cache is cache struct.
type Cache[Key, Value any] struct {
cache sync.Map
m Map[Key, *item[Value]]
autoRenew bool
}

Expand All @@ -57,7 +53,9 @@ func New[Key, Value any](autoRenew bool) *Cache[Key, Value] {
// Set sets cache value for a key, if fn is presented, this value will regenerate when expired.
func (c *Cache[Key, Value]) Set(key Key, value Value, lifecycle time.Duration, fn func() (Value, error)) {
i := &item[Value]{lifecycle: lifecycle, fn: fn}
i.Context, i.cancel = newContext(value, lifecycle)
i.Lock()
defer i.Unlock()
i.set(value)
if c.autoRenew && lifecycle > 0 {
go func() {
for {
Expand All @@ -74,45 +72,66 @@ func (c *Cache[Key, Value]) Set(key Key, value Value, lifecycle time.Duration, f
}
}()
}
c.cache.Store(key, i)
c.m.Store(key, i)
}

// Get gets cache value by key and whether value was found.
func (c *Cache[Key, Value]) Get(key Key) (Value, bool) {
v, ok := c.cache.Load(key)
if !ok {
return *new(Value), false
func (c *Cache[Key, Value]) get(key Key) (i *item[Value], ok bool) {
if i, ok = c.m.Load(key); !ok {
return
}
if i := v.(*item[Value]); !c.autoRenew && i.Err() == context.DeadlineExceeded {
if !c.autoRenew && i.Err() == context.DeadlineExceeded {
if i.fn == nil {
c.Delete(key)
return *new(Value), false
return nil, false
}
return i.renew(), true
} else {
return i.value(), true
i.renew()
}
return
}

// Get gets cache value by key and whether value was found.
func (c *Cache[Key, Value]) Get(key Key) (value Value, ok bool) {
var i *item[Value]
if i, ok = c.get(key); !ok {
return
}
i.Lock()
defer i.Unlock()
value = i.value()
return
}

// Delete deletes the value for a key.
func (c *Cache[Key, Value]) Delete(key Key) {
if v, ok := c.cache.LoadAndDelete(key); ok {
if v, ok := v.(*item[Value]); ok {
if v.cancel != nil {
v.cancel()
}
if i, ok := c.m.LoadAndDelete(key); ok {
i.Lock()
defer i.Unlock()
if i.cancel != nil {
i.cancel()
}
}
}

// Swap swaps the value for a key and returns the previous value if any. The loaded result reports whether the key was present.
func (c *Cache[Key, Value]) Swap(key Key, value Value) (previous Value, loaded bool) {
var i *item[Value]
if i, loaded = c.get(key); loaded {
i.Lock()
defer i.Unlock()
previous = i.value()
i.set(value)
}
return
}

// Empty deletes all values in cache.
func (c *Cache[Key, Value]) Empty() {
c.cache.Range(func(k, v any) bool {
c.cache.Delete(k)
if v, ok := v.(*item[Value]); ok {
if v.cancel != nil {
v.cancel()
}
c.m.Range(func(k Key, i *item[Value]) bool {
c.m.Delete(k)
i.Lock()
defer i.Unlock()
if i.cancel != nil {
i.cancel()
}
return true
})
Expand Down
13 changes: 12 additions & 1 deletion cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,24 @@ import (
"time"
)

func TestSetGetDelete(t *testing.T) {
func TestCache(t *testing.T) {
cache := New[string, string](false)
cache.Set("key", "value", 0, nil)
if value, ok := cache.Get("key"); !ok {
t.Fatal("expected ok; got not")
} else if value != "value" {
t.Errorf("expected value; got %q", value)
}
if value, ok := cache.Swap("key", "swap"); !ok {
t.Fatal("expected ok; got not")
} else if value != "value" {
t.Errorf("expected value; got %q", value)
}
if value, ok := cache.Get("key"); !ok {
t.Fatal("expected ok; got not")
} else if value != "swap" {
t.Errorf("expected swap; got %q", value)
}
cache.Delete("key")
if _, ok := cache.Get("key"); ok {
t.Error("expected not ok; got ok")
Expand All @@ -39,6 +49,7 @@ func TestEmpty(t *testing.T) {

func TestRenew(t *testing.T) {
cache := New[string, string](true)
defer cache.Empty()
expire := make(chan struct{})
cache.Set("renew", "old", 2*time.Second, func() (string, error) {
defer func() { close(expire) }()
Expand Down
73 changes: 73 additions & 0 deletions cache/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package cache

import "sync"

type Map[Key, Value any] struct {
m sync.Map
}

func NewMap[Key, Value any]() *Map[Key, Value] {
return &Map[Key, Value]{}
}

func (m *Map[Key, Value]) Load(key Key) (value Value, ok bool) {
var v any
if v, ok = m.m.Load(key); ok && v != nil {
value = v.(Value)
}
return
}

func (m *Map[Key, Value]) Store(key Key, value Value) {
m.m.Store(key, value)
}

func (m *Map[Key, Value]) LoadOrStore(key Key, value Value) (actual Value, loaded bool) {
var v any
if v, loaded = m.m.LoadOrStore(key, value); v != nil {
actual = v.(Value)
}
return
}

func (m *Map[Key, Value]) LoadAndDelete(key Key) (value Value, loaded bool) {
var v any
if v, loaded = m.m.LoadAndDelete(key); loaded && v != nil {
value = v.(Value)
}
return
}

func (m *Map[Key, Value]) Delete(key Key) {
m.m.Delete(key)
}

func (m *Map[Key, Value]) Swap(key Key, value Value) (previous Value, loaded bool) {
var v any
if v, loaded = m.m.Swap(key, value); loaded && v != nil {
previous = v.(Value)
}
return
}

func (m *Map[Key, Value]) CompareAndSwap(key Key, old Value, new Value) bool {
return m.m.CompareAndSwap(key, old, new)
}

func (m *Map[Key, Value]) CompareAndDelete(key Key, old Value) (deleted bool) {
return m.m.CompareAndDelete(key, old)
}

func (m *Map[Key, Value]) Range(f func(Key, Value) bool) {
m.m.Range(func(key, value any) bool {
var k Key
if key != nil {
k = key.(Key)
}
var v Value
if value != nil {
v = value.(Value)
}
return f(k, v)
})
}
Loading

0 comments on commit 56f0550

Please sign in to comment.