Skip to content

Commit 8b10a23

Browse files
authored
Add ShouldUpdate() function in config (#427)
Add ShouldUpdate() function which allows users to check whether the value should be updated in the cache on Set.
1 parent c9bd229 commit 8b10a23

File tree

3 files changed

+59
-2
lines changed

3 files changed

+59
-2
lines changed

cache.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,16 @@ type Config[K Key, V any] struct {
148148
// as well as on rejection of the value.
149149
OnExit func(val V)
150150

151+
// ShouldUpdate is called when a value already exists in cache and is being updated.
152+
// If ShouldUpdate returns true, the cache continues with the update (Set). If the
153+
// function returns false, no changes are made in the cache. If the value doesn't
154+
// already exist, the cache continue with setting that value for the given key.
155+
//
156+
// In this function, you can check whether the new value is valid. For example, if
157+
// your value has timestamp assosicated with it, you could check whether the new
158+
// value has the latest timestamp, preventing you from setting an older value.
159+
ShouldUpdate func(cur, prev V) bool
160+
151161
// KeyToHash function is used to customize the key hashing algorithm.
152162
// Each key will be hashed using the provided function. If keyToHash value
153163
// is not set, the default keyToHash function is used.
@@ -233,6 +243,7 @@ func NewCache[K Key, V any](config *Config[K, V]) (*Cache[K, V], error) {
233243
ignoreInternalCost: config.IgnoreInternalCost,
234244
cleanupTicker: time.NewTicker(time.Duration(config.TtlTickerDurationInSec) * time.Second / 2),
235245
}
246+
cache.storedItems.SetShouldUpdateFn(config.ShouldUpdate)
236247
cache.onExit = func(val V) {
237248
if config.OnExit != nil {
238249
config.OnExit(val)

store.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"time"
2222
)
2323

24+
type updateFn[V any] func(cur, prev V) bool
25+
2426
// TODO: Do we need this to be a separate struct from Item?
2527
type storeItem[V any] struct {
2628
key uint64
@@ -53,6 +55,7 @@ type store[V any] interface {
5355
Cleanup(policy *defaultPolicy[V], onEvict func(item *Item[V]))
5456
// Clear clears all contents of the store.
5557
Clear(onEvict func(item *Item[V]))
58+
SetShouldUpdateFn(f updateFn[V])
5659
}
5760

5861
// newStore returns the default store implementation.
@@ -78,6 +81,12 @@ func newShardedMap[V any]() *shardedMap[V] {
7881
return sm
7982
}
8083

84+
func (m *shardedMap[V]) SetShouldUpdateFn(f updateFn[V]) {
85+
for i := range m.shards {
86+
m.shards[i].setShouldUpdateFn(f)
87+
}
88+
}
89+
8190
func (sm *shardedMap[V]) Get(key, conflict uint64) (V, bool) {
8291
return sm.shards[key%numShards].get(key, conflict)
8392
}
@@ -116,17 +125,25 @@ func (sm *shardedMap[V]) Clear(onEvict func(item *Item[V])) {
116125

117126
type lockedMap[V any] struct {
118127
sync.RWMutex
119-
data map[uint64]storeItem[V]
120-
em *expirationMap[V]
128+
data map[uint64]storeItem[V]
129+
em *expirationMap[V]
130+
shouldUpdate updateFn[V]
121131
}
122132

123133
func newLockedMap[V any](em *expirationMap[V]) *lockedMap[V] {
124134
return &lockedMap[V]{
125135
data: make(map[uint64]storeItem[V]),
126136
em: em,
137+
shouldUpdate: func(cur, prev V) bool {
138+
return true
139+
},
127140
}
128141
}
129142

143+
func (m *lockedMap[V]) setShouldUpdateFn(f updateFn[V]) {
144+
m.shouldUpdate = f
145+
}
146+
130147
func (m *lockedMap[V]) get(key, conflict uint64) (V, bool) {
131148
m.RLock()
132149
item, ok := m.data[key]
@@ -167,6 +184,9 @@ func (m *lockedMap[V]) Set(i *Item[V]) {
167184
if i.Conflict != 0 && (i.Conflict != item.conflict) {
168185
return
169186
}
187+
if m.shouldUpdate != nil && !m.shouldUpdate(i.Value, item.value) {
188+
return
189+
}
170190
m.em.update(i.Key, i.Conflict, item.expiration, i.Expiration)
171191
} else {
172192
// The value is not in the map already. There's no need to return anything.
@@ -211,6 +231,9 @@ func (m *lockedMap[V]) Update(newItem *Item[V]) (V, bool) {
211231
if newItem.Conflict != 0 && (newItem.Conflict != item.conflict) {
212232
return zeroValue[V](), false
213233
}
234+
if m.shouldUpdate != nil && !m.shouldUpdate(newItem.Value, item.value) {
235+
return item.value, false
236+
}
214237

215238
m.em.update(newItem.Key, newItem.Conflict, item.expiration, newItem.Expiration)
216239
m.data[newItem.Key] = storeItem[V]{

store_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,29 @@ func TestStoreClear(t *testing.T) {
7676
}
7777
}
7878

79+
func TestShouldUpdate(t *testing.T) {
80+
// Create a should update function where the value only increases.
81+
s := newStore[int]()
82+
s.SetShouldUpdateFn(func(cur, prev int) bool {
83+
return cur > prev
84+
})
85+
86+
key, conflict := z.KeyToHash(1)
87+
i := Item[int]{
88+
Key: key,
89+
Conflict: conflict,
90+
Value: 2,
91+
}
92+
s.Set(&i)
93+
i.Value = 1
94+
_, ok := s.Update(&i)
95+
require.False(t, ok)
96+
97+
i.Value = 3
98+
_, ok = s.Update(&i)
99+
require.True(t, ok)
100+
}
101+
79102
func TestStoreUpdate(t *testing.T) {
80103
s := newStore[int]()
81104
key, conflict := z.KeyToHash(1)

0 commit comments

Comments
 (0)