Skip to content

Commit 8ceb9ac

Browse files
committed
Added RefreshIfModifiedAfter function for LoadingCache
Function will asynchronously reloads value for Key if the provided timestamp indicates the data was modified after the entry was last loaded
1 parent bfa088e commit 8ceb9ac

File tree

3 files changed

+97
-5
lines changed

3 files changed

+97
-5
lines changed

cache.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// including support for LRU, Segmented LRU and TinyLFU.
33
package cache
44

5+
import "time"
6+
57
// Key is any value which is comparable.
68
// See http://golang.org/ref/spec#Comparison_operators for details.
79
type Key interface{}
@@ -65,6 +67,11 @@ type LoadingCache interface {
6567
// will continue to be returned by Get while the new value is loading.
6668
// If Key does not exist, this function will block until the value is loaded.
6769
Refresh(Key)
70+
71+
// RefreshIfModifiedAfter asynchronously reloads value for Key if the provided timestamp
72+
// indicates the data was modified after the entry was last loaded. If the entry doesn't exist,
73+
// it will synchronously load and block until the value is loaded.
74+
RefreshIfModifiedAfter(Key, time.Time)
6875
}
6976

7077
// LoaderFunc retrieves the value corresponding to given Key.

local.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,17 +195,17 @@ func (c *localCache) GetActive(k Key) (Value, error) {
195195
return nil, err
196196
}
197197
en := c.cache.get(k, sum(k))
198-
if en != nil && ! en.getInvalidated() {
198+
if en != nil && !en.getInvalidated() {
199199
return obj, nil
200200
}
201-
return nil, errors.New ("entry invalidated")
201+
return nil, errors.New("entry invalidated")
202202
}
203203

204204
// GetAllKeys returns all keys.
205205
func (c *localCache) GetAllKeys() []interface{} {
206206
keys := make([]interface{}, 0, c.cache.len())
207207
c.cache.walk(func(en *entry) {
208-
if ! en.getInvalidated() {
208+
if !en.getInvalidated() {
209209
keys = append(keys, en.key)
210210
}
211211
})
@@ -216,7 +216,7 @@ func (c *localCache) GetAllKeys() []interface{} {
216216
func (c *localCache) GetAllValues() []interface{} {
217217
values := make([]interface{}, 0, c.cache.len())
218218
c.cache.walk(func(en *entry) {
219-
if ! en.getInvalidated() {
219+
if !en.getInvalidated() {
220220
values = append(values, en.getValue())
221221
}
222222
})
@@ -227,7 +227,7 @@ func (c *localCache) GetAllValues() []interface{} {
227227
func (c *localCache) GetAll() map[interface{}]interface{} {
228228
var values = make(map[interface{}]interface{}, c.cache.len())
229229
c.cache.walk(func(en *entry) {
230-
if ! en.getInvalidated() {
230+
if !en.getInvalidated() {
231231
values[en.key] = en.getValue()
232232
}
233233
})
@@ -253,6 +253,24 @@ func (c *localCache) Refresh(k Key) {
253253
}
254254
}
255255

256+
// RefreshIfModifiedAfter asynchronously reloads value for Key if the provided timestamp
257+
// indicates the data was modified after the entry was last loaded. If the entry doesn't exist,
258+
// it will synchronously load and block until the value is loaded.
259+
func (c *localCache) RefreshIfModifiedAfter(k Key, modifiedTime time.Time) {
260+
if c.loader == nil {
261+
return
262+
}
263+
en := c.cache.get(k, sum(k))
264+
if en == nil {
265+
c.load(k)
266+
} else {
267+
// Only refresh if the data was modified after the entry was last loaded
268+
if modifiedTime.UnixNano() > en.getWriteTime() {
269+
c.refreshAsync(en)
270+
}
271+
}
272+
}
273+
256274
// Stats copies cache stats to t.
257275
func (c *localCache) Stats(t *Stats) {
258276
c.stats.Snapshot(t)

local_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,73 @@ func TestRefreshAterWrite(t *testing.T) {
395395
}
396396
}
397397

398+
func TestRefreshIfModifiedAfter(t *testing.T) {
399+
loadCount := 0
400+
loader := func(k Key) (Value, error) {
401+
loadCount++
402+
return loadCount, nil
403+
}
404+
wg := sync.WaitGroup{}
405+
insFunc := func(Key, Value) {
406+
wg.Done()
407+
}
408+
mockTime := newMockTime()
409+
currentTime = mockTime.now
410+
c := NewLoadingCache(loader, WithRefreshAfterWrite(1*time.Minute),
411+
WithReloader(&syncReloader{loader}), withInsertionListener(insFunc))
412+
defer c.Close()
413+
414+
key := "mykey"
415+
416+
// 1. Entry does not exist, should load synchronously.
417+
wg.Add(1)
418+
c.RefreshIfModifiedAfter(key, mockTime.now())
419+
wg.Wait()
420+
421+
if loadCount != 1 {
422+
t.Fatalf("expected loadCount to be 1 after initial load, got %d", loadCount)
423+
}
424+
425+
// 2. Entry exists, but modifiedTime is not after entry's write time. Should not refresh.
426+
c.RefreshIfModifiedAfter(key, mockTime.now().Add(-1*time.Millisecond)) // modified time is before write time
427+
// No wg.Add/Wait as it should be synchronous and do nothing.
428+
if loadCount != 1 {
429+
t.Fatalf("expected loadCount to be 1 when not modified, got %d", loadCount)
430+
}
431+
432+
// 3. Entry exists, and modifiedTime is after entry's write time. Should refresh.
433+
wg.Add(1)
434+
c.RefreshIfModifiedAfter(key, mockTime.now().Add(1*time.Millisecond)) // modified time is after write time
435+
wg.Wait()
436+
if loadCount != 2 {
437+
t.Fatalf("expected loadCount to be 2 after refresh, got %d", loadCount)
438+
}
439+
440+
// 4. Manually put new value, then refresh with old modifiedTime, should not refresh.
441+
key2 := "mykey2"
442+
443+
wg.Add(1)
444+
c.Put(key2, 1)
445+
wg.Wait()
446+
v, err := c.Get(key2)
447+
if err != nil || v.(int) != 1 {
448+
t.Fatalf("unexpected get: %v %v", v, err)
449+
}
450+
451+
c.RefreshIfModifiedAfter(key2, mockTime.now()) // modified time is the same as write time
452+
// No wg.Add/Wait as it should be synchronous and do nothing.
453+
if loadCount != 2 {
454+
t.Fatalf("expected loadCount to be 2 when not modified, got %d", loadCount)
455+
}
456+
457+
wg.Add(1)
458+
c.RefreshIfModifiedAfter(key, mockTime.now().Add(1*time.Millisecond)) // modified time is after write time
459+
wg.Wait()
460+
if loadCount != 3 {
461+
t.Fatalf("expected loadCount to be 3 after refresh, got %d", loadCount)
462+
}
463+
}
464+
398465
func TestGetIfPresentExpired(t *testing.T) {
399466
wg := sync.WaitGroup{}
400467
insFunc := func(Key, Value) {

0 commit comments

Comments
 (0)