Skip to content

Commit

Permalink
🔄 Sync from monorepo
Browse files Browse the repository at this point in the history
  • Loading branch information
mojo-machine[bot] committed Apr 18, 2024
1 parent 11b4643 commit 4660c1b
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 22 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
98 changes: 79 additions & 19 deletions lib/ttlcache/keyed_cache.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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()
Expand All @@ -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
}
1 change: 1 addition & 0 deletions lib/ttlcache/singular_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

0 comments on commit 4660c1b

Please sign in to comment.