From 4660c1bdb1be68d77d8796cfd5d1e9899aafb8bb Mon Sep 17 00:00:00 2001 From: "mojo-machine[bot]" <111131124+mojo-machine[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 14:08:17 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20Sync=20from=20monorepo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/wearemojo/mojo/commit/0b9d512c00399c6182b481887276b838611de173 --- go.mod | 2 +- go.sum | 4 +- lib/ttlcache/keyed_cache.go | 98 +++++++++++++++++++++++++++------- lib/ttlcache/singular_cache.go | 1 + 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 352e6c9..2bc1901 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index 1adc08d..89343d0 100644 --- a/go.sum +++ b/go.sum @@ -227,8 +227,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc= -google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s= +google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38= +google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= diff --git a/lib/ttlcache/keyed_cache.go b/lib/ttlcache/keyed_cache.go index 8f7c470..598e6a0 100644 --- a/lib/ttlcache/keyed_cache.go +++ b/lib/ttlcache/keyed_cache.go @@ -1,10 +1,15 @@ package ttlcache import ( + "fmt" "sync" "time" + + "github.com/wearemojo/mojo-public-go/lib/slicefn" ) +const TTLForever = -1 + type CachedItem[T any] struct { Value T SetAt time.Time @@ -17,6 +22,7 @@ type KeyedCache[TKey comparable, TVal any] struct { lock sync.RWMutex } +// a TTL of -1 means that items never expire func NewKeyed[TKey comparable, TVal any](ttl time.Duration) *KeyedCache[TKey, TVal] { return &KeyedCache[TKey, TVal]{ ttl: ttl, @@ -38,11 +44,11 @@ func (c *KeyedCache[TKey, TVal]) Get(key TKey) (item CachedItem[TVal], ok bool) } func (c *KeyedCache[TKey, TVal]) GetMany(keys []TKey) map[TKey]CachedItem[TVal] { + res := make(map[TKey]CachedItem[TVal], len(keys)) + c.lock.RLock() defer c.lock.RUnlock() - res := make(map[TKey]CachedItem[TVal], len(keys)) - for _, key := range keys { if item, ok := c.items[key]; ok { res[key] = item @@ -53,21 +59,23 @@ func (c *KeyedCache[TKey, TVal]) GetMany(keys []TKey) map[TKey]CachedItem[TVal] } func (c *KeyedCache[TKey, TVal]) Set(key TKey, value TVal) { + now := time.Now() + c.lock.Lock() defer c.lock.Unlock() c.items[key] = CachedItem[TVal]{ Value: value, - SetAt: time.Now(), + SetAt: now, } } func (c *KeyedCache[TKey, TVal]) SetMany(items map[TKey]TVal) { + now := time.Now() + c.lock.Lock() defer c.lock.Unlock() - now := time.Now() - for key, item := range items { c.items[key] = CachedItem[TVal]{ Value: item, @@ -77,24 +85,15 @@ func (c *KeyedCache[TKey, TVal]) SetMany(items map[TKey]TVal) { } func (c *KeyedCache[TKey, TVal]) GetOrDo(key TKey, fn func() TVal) TVal { - if item, ok := c.Get(key); ok { - if time.Since(item.SetAt) < c.ttl { - return item.Value - } - } - - value := fn() - - c.Set(key, value) - + value, _ := c.GetOrDoE(key, func() (TVal, error) { + return fn(), nil + }) return value } func (c *KeyedCache[TKey, TVal]) GetOrDoE(key TKey, fn func() (TVal, error)) (TVal, error) { - if item, ok := c.Get(key); ok { - if time.Since(item.SetAt) < c.ttl { - return item.Value, nil - } + if item, ok := c.Get(key); ok && (c.ttl == TTLForever || time.Since(item.SetAt) < c.ttl) { + return item.Value, nil } value, err := fn() @@ -106,3 +105,64 @@ func (c *KeyedCache[TKey, TVal]) GetOrDoE(key TKey, fn func() (TVal, error)) (TV return value, nil } + +func (c *KeyedCache[TKey, TVal]) GetOrDoMany(keys []TKey, fn func([]TKey) []TVal) []TVal { + res, _ := c.GetOrDoManyE(keys, func(keys []TKey) ([]TVal, error) { + return fn(keys), nil + }) + return res +} + +func (c *KeyedCache[TKey, TVal]) GetOrDoManyE(keys []TKey, fn func([]TKey) ([]TVal, error)) ([]TVal, error) { + // although `GetMany` and `SetMany` could be used in this function, we can + // reduce the number of loops by doing everything together in a single loop + + res := make([]TVal, len(keys)) + missingKeyIndices := make([]int, 0, len(keys)) + + (func() { + c.lock.RLock() + defer c.lock.RUnlock() + + now := time.Now() + + for idx, key := range keys { + if item, ok := c.items[key]; ok && (c.ttl == TTLForever || now.Sub(item.SetAt) < c.ttl) { + res[idx] = item.Value + } else { + missingKeyIndices = append(missingKeyIndices, idx) + } + } + })() + + if len(missingKeyIndices) == 0 { + return res, nil + } + + missingKeys := slicefn.Map(missingKeyIndices, func(idx int) TKey { return keys[idx] }) + + newItems, err := fn(missingKeys) + if err != nil { + return res, err + } else if len(newItems) != len(missingKeys) { + panic(fmt.Sprintf("fn returned %d items, expected %d", len(newItems), len(missingKeys))) + } + + now := time.Now() + + c.lock.Lock() + defer c.lock.Unlock() + + for idx, key := range missingKeys { + newItem := newItems[idx] + + c.items[key] = CachedItem[TVal]{ + Value: newItem, + SetAt: now, + } + + res[missingKeyIndices[idx]] = newItem + } + + return res, nil +} diff --git a/lib/ttlcache/singular_cache.go b/lib/ttlcache/singular_cache.go index d49b6bf..3bb4334 100644 --- a/lib/ttlcache/singular_cache.go +++ b/lib/ttlcache/singular_cache.go @@ -10,6 +10,7 @@ type SingularCache[T any] struct { cache *KeyedCache[struct{}, T] } +// a TTL of -1 means that items never expire func NewSingular[T any](ttl time.Duration) *SingularCache[T] { return &SingularCache[T]{ cache: NewKeyed[struct{}, T](ttl),