Skip to content

Commit cb66502

Browse files
committed
add time to live lru cache
1 parent bd90477 commit cb66502

File tree

4 files changed

+159
-50
lines changed

4 files changed

+159
-50
lines changed

util/cache/ttllru/mocks/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
//go:generate ../../../../hack/tools/bin/mockgen -destination ttllru_mock.go -package mock_ttllru -source ../ttllru.go cacher
17+
//go:generate ../../../../hack/tools/bin/mockgen -destination ttllru_mock.go -package mock_ttllru -source ../ttllru.go Cacher PeekingCacher
1818
//go:generate /usr/bin/env bash -c "cat ../../../../hack/boilerplate/boilerplate.generatego.txt ttllru_mock.go > _ttllru_mock.go && mv _ttllru_mock.go ttllru_mock.go"
1919
package mock_ttllru //nolint

util/cache/ttllru/mocks/ttllru_mock.go

Lines changed: 103 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

util/cache/ttllru/ttllru.go

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,24 @@ type (
2828
// Cache is a TTL LRU cache which caches items with a max time to live and with
2929
// bounded length
3030
Cache struct {
31+
Cacher
3132
TimeToLive time.Duration
32-
cache cacher
3333
mu sync.Mutex
3434
}
3535

36-
cacher interface {
36+
// Cacher describes a basic cache
37+
Cacher interface {
3738
Get(key interface{}) (value interface{}, ok bool)
3839
Add(key interface{}, value interface{}) (evicted bool)
3940
Remove(key interface{}) (ok bool)
4041
}
4142

43+
// PeekingCacher describes a basic cache with the ability to peek
44+
PeekingCacher interface {
45+
Cacher
46+
Peek(key interface{}) (value interface{}, expiration time.Time, ok bool)
47+
}
48+
4249
timeToLiveItem struct {
4350
LastTouch time.Time
4451
Value interface{}
@@ -47,7 +54,7 @@ type (
4754

4855
// New creates a new TTL LRU cache which caches items with a max time to live and with
4956
// bounded length
50-
func New(size int, timeToLive time.Duration) (*Cache, error) {
57+
func New(size int, timeToLive time.Duration) (PeekingCacher, error) {
5158
c, err := lru.New(size)
5259
if err != nil {
5360
return nil, errors.Wrap(err, "failed to build new LRU cache")
@@ -56,19 +63,51 @@ func New(size int, timeToLive time.Duration) (*Cache, error) {
5663
return newCache(timeToLive, c)
5764
}
5865

59-
func newCache(timeToLive time.Duration, cache cacher) (*Cache, error) {
66+
func newCache(timeToLive time.Duration, cache Cacher) (PeekingCacher, error) {
6067
return &Cache{
61-
cache: cache,
68+
Cacher: cache,
6269
TimeToLive: timeToLive,
6370
}, nil
6471
}
6572

6673
// Get returns a value and a bool indicating the value was found for a given key
6774
func (ttlCache *Cache) Get(key interface{}) (value interface{}, ok bool) {
75+
ttlItem, ok := ttlCache.peekItem(key)
76+
if !ok {
77+
return nil, false
78+
}
79+
80+
ttlItem.LastTouch = time.Now()
81+
return ttlItem.Value, true
82+
}
83+
84+
// Add will add a value for a given key
85+
func (ttlCache *Cache) Add(key interface{}, val interface{}) bool {
6886
ttlCache.mu.Lock()
6987
defer ttlCache.mu.Unlock()
7088

71-
val, ok := ttlCache.cache.Get(key)
89+
return ttlCache.Cacher.Add(key, &timeToLiveItem{
90+
Value: val,
91+
LastTouch: time.Now(),
92+
})
93+
}
94+
95+
// Peek will fetch an item from the cache, but will not update the expiration time
96+
func (ttlCache *Cache) Peek(key interface{}) (value interface{}, expiration time.Time, ok bool) {
97+
ttlItem, ok := ttlCache.peekItem(key)
98+
if !ok {
99+
return nil, time.Time{}, false
100+
}
101+
102+
expirationTime := time.Now().Add(ttlCache.TimeToLive - time.Since(ttlItem.LastTouch))
103+
return ttlItem.Value, expirationTime, true
104+
}
105+
106+
func (ttlCache *Cache) peekItem(key interface{}) (value *timeToLiveItem, ok bool) {
107+
ttlCache.mu.Lock()
108+
defer ttlCache.mu.Unlock()
109+
110+
val, ok := ttlCache.Cacher.Get(key)
72111
if !ok {
73112
return nil, false
74113
}
@@ -79,21 +118,9 @@ func (ttlCache *Cache) Get(key interface{}) (value interface{}, ok bool) {
79118
}
80119

81120
if time.Since(ttlItem.LastTouch) > ttlCache.TimeToLive {
82-
ttlCache.cache.Remove(key)
121+
ttlCache.Cacher.Remove(key)
83122
return nil, false
84123
}
85124

86-
ttlItem.LastTouch = time.Now()
87-
return ttlItem.Value, true
88-
}
89-
90-
// Add will add a value for a given key
91-
func (ttlCache *Cache) Add(key interface{}, val interface{}) {
92-
ttlCache.mu.Lock()
93-
defer ttlCache.mu.Unlock()
94-
95-
ttlCache.cache.Add(key, &timeToLiveItem{
96-
Value: val,
97-
LastTouch: time.Now(),
98-
})
125+
return ttlItem, true
99126
}

util/cache/ttllru/ttllru_test.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323

2424
"github.com/golang/mock/gomock"
2525
. "github.com/onsi/gomega"
26-
2726
gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock"
2827
mockttllru "sigs.k8s.io/cluster-api-provider-azure/util/cache/ttllru/mocks"
2928
)
@@ -42,7 +41,7 @@ func TestNew(t *testing.T) {
4241
func TestCache_Add(t *testing.T) {
4342
g := NewWithT(t)
4443
mockCtrl := gomock.NewController(t)
45-
mockCache := mockttllru.NewMockcacher(mockCtrl)
44+
mockCache := mockttllru.NewMockCacher(mockCtrl)
4645
defer mockCtrl.Finish()
4746
subject, err := newCache(defaultCacheDuration, mockCache)
4847
g.Expect(err).Should(BeNil())
@@ -78,11 +77,11 @@ func TestCache_Get(t *testing.T) {
7877

7978
cases := []struct {
8079
Name string
81-
TestCase func(g *GomegaWithT, subject *Cache, mock *mockttllru.Mockcacher)
80+
TestCase func(g *GomegaWithT, subject PeekingCacher, mock *mockttllru.MockCacher)
8281
}{
8382
{
8483
Name: "NoItemsInCache",
85-
TestCase: func(g *GomegaWithT, subject *Cache, mock *mockttllru.Mockcacher) {
84+
TestCase: func(g *GomegaWithT, subject PeekingCacher, mock *mockttllru.MockCacher) {
8685
key := "not_there"
8786
mock.EXPECT().Get(key).Return(nil, false)
8887
val, ok := subject.Get(key)
@@ -92,7 +91,7 @@ func TestCache_Get(t *testing.T) {
9291
},
9392
{
9493
Name: "ExistingItemNotExpired",
95-
TestCase: func(g *GomegaWithT, subject *Cache, mock *mockttllru.Mockcacher) {
94+
TestCase: func(g *GomegaWithT, subject PeekingCacher, mock *mockttllru.MockCacher) {
9695
mock.EXPECT().Get(key).Return(&timeToLiveItem{
9796
LastTouch: time.Now(),
9897
Value: value,
@@ -104,7 +103,7 @@ func TestCache_Get(t *testing.T) {
104103
},
105104
{
106105
Name: "ExistingItemExpired",
107-
TestCase: func(g *GomegaWithT, subject *Cache, mock *mockttllru.Mockcacher) {
106+
TestCase: func(g *GomegaWithT, subject PeekingCacher, mock *mockttllru.MockCacher) {
108107
mock.EXPECT().Get(key).Return(&timeToLiveItem{
109108
LastTouch: time.Now().Add(-(10*time.Second + defaultCacheDuration)),
110109
Value: value,
@@ -117,7 +116,7 @@ func TestCache_Get(t *testing.T) {
117116
},
118117
{
119118
Name: "ExistingItemGetAdvancesLastTouch",
120-
TestCase: func(g *GomegaWithT, subject *Cache, mock *mockttllru.Mockcacher) {
119+
TestCase: func(g *GomegaWithT, subject PeekingCacher, mock *mockttllru.MockCacher) {
121120
lastTouch := time.Now().Add(defaultCacheDuration - 10*time.Second)
122121
item := &timeToLiveItem{
123122
LastTouch: lastTouch,
@@ -133,7 +132,7 @@ func TestCache_Get(t *testing.T) {
133132
},
134133
{
135134
Name: "ExistingItemIsNotTTLItem",
136-
TestCase: func(g *GomegaWithT, subject *Cache, mock *mockttllru.Mockcacher) {
135+
TestCase: func(g *GomegaWithT, subject PeekingCacher, mock *mockttllru.MockCacher) {
137136
item := &struct {
138137
Value string
139138
}{
@@ -153,7 +152,7 @@ func TestCache_Get(t *testing.T) {
153152
t.Run(c.Name, func(t *testing.T) {
154153
g := NewWithT(t)
155154
mockCtrl := gomock.NewController(t)
156-
mockCache := mockttllru.NewMockcacher(mockCtrl)
155+
mockCache := mockttllru.NewMockCacher(mockCtrl)
157156
defer mockCtrl.Finish()
158157
subject, err := newCache(defaultCacheDuration, mockCache)
159158
g.Expect(err).Should(BeNil())

0 commit comments

Comments
 (0)