Skip to content

Commit 8110d5a

Browse files
Tyson AndreTysonAndre-tmg
Tyson Andre
authored andcommitted
Don't create a timed flusher goroutine or dirtyList if there is no flusher
This is an unnecessary goroutine for uses such as the apns module in uniqush-push. Additionally, dirtyList was never emptied if the flusher was nil, so this was a memory leak. Also, update README
1 parent df3169e commit 8110d5a

File tree

3 files changed

+186
-26
lines changed

3 files changed

+186
-26
lines changed

README.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
cache
2-
=====
1+
cache2
2+
======
33

44
A cache implementation in Go
55

66
This is used in [uniqush-push]
77

8+
Because some installations of uniqush-push predate versioned dependencies,
9+
this was created as a repo separate from [uniqush/cache],
10+
because of the inconvenience caused by interface incompatibilities.
11+
812
[uniqush-push]: http://uniqush.org
13+
[uniqush/cache]: https://github.com/uniqush/cache
914

1015
License: Apache-2.0
11-

cache.go

+103-10
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*
1616
*/
1717

18-
package cache
18+
package cache2
1919

2020
import (
2121
"container/list"
@@ -41,6 +41,24 @@ type cacheItem struct {
4141
value interface{}
4242
}
4343

44+
type CacheInterface interface {
45+
Len() int
46+
Set(key string, value interface{})
47+
Get(key string) interface{}
48+
Delete(key string) interface{}
49+
Flush()
50+
debug()
51+
}
52+
53+
type SimpleCache struct {
54+
mu sync.Mutex
55+
data map[string]*list.Element
56+
list *list.List
57+
capacity int
58+
}
59+
60+
var _ CacheInterface = &SimpleCache{}
61+
4462
type Cache struct {
4563
mu sync.Mutex
4664
flushPeriod time.Duration
@@ -52,16 +70,21 @@ type Cache struct {
5270
maxNrDirty int
5371
}
5472

73+
var _ CacheInterface = &Cache{}
74+
75+
func (c *SimpleCache) Len() int {
76+
return len(c.data)
77+
}
78+
5579
func (c *Cache) Len() int {
5680
return len(c.data)
5781
}
5882

83+
func (c *SimpleCache) Flush() {}
84+
5985
func (c *Cache) Flush() {
6086
c.mu.Lock()
6187
defer c.mu.Unlock()
62-
if c.flusher == nil {
63-
return
64-
}
6588
for e := c.dirtyList.Front(); e != nil; e = e.Next() {
6689
if de, ok := e.Value.(*dirtyElement); ok {
6790
if de.removed {
@@ -74,6 +97,16 @@ func (c *Cache) Flush() {
7497
c.dirtyList = list.New()
7598
}
7699

100+
func (c *SimpleCache) debug() {
101+
fmt.Printf("nr elems %v <= %v", c.list.Len(), c.capacity)
102+
fmt.Println("-----------------elements------------")
103+
for e := c.list.Front(); e != nil; e = e.Next() {
104+
item := e.Value.(*cacheItem)
105+
fmt.Printf("%v: %v\n", item.key, item.value)
106+
}
107+
fmt.Println("-------------------------------------")
108+
}
109+
77110
func (c *Cache) debug() {
78111
fmt.Printf("nr elems %v <= %v, nr dirty elems %v < %v\n",
79112
c.list.Len(), c.capacity, c.dirtyList.Len(), c.maxNrDirty)
@@ -112,15 +145,19 @@ func (c *Cache) checkAndFlush() {
112145
// flushPeriod = 0 second means no periodically flush;
113146
// undefined in range (0, 1).
114147
func New(capacity int, maxNrDirty int, flushPeriod time.Duration, flusher Flusher) *Cache {
148+
initialCapacity := capacity
149+
if initialCapacity < 0 {
150+
initialCapacity = 1024
151+
}
152+
if flusher == nil {
153+
panic("Should use NewSimple")
154+
}
155+
115156
cache := new(Cache)
116157

117158
cache.flushPeriod = flushPeriod
118-
cache.capacity = capacity
119-
if cache.capacity < 0 {
120-
cache.data = make(map[string]*list.Element, 1024)
121-
} else {
122-
cache.data = make(map[string]*list.Element, cache.capacity)
123-
}
159+
cache.capacity = initialCapacity
160+
cache.data = make(map[string]*list.Element, initialCapacity)
124161
cache.list = list.New()
125162
cache.dirtyList = list.New()
126163
cache.flusher = flusher
@@ -137,6 +174,29 @@ func New(capacity int, maxNrDirty int, flushPeriod time.Duration, flusher Flushe
137174
return cache
138175
}
139176

177+
func NewSimple(capacity int) *SimpleCache {
178+
initialCapacity := capacity
179+
if initialCapacity < 0 {
180+
initialCapacity = 1024
181+
}
182+
return &SimpleCache{
183+
data: make(map[string]*list.Element, initialCapacity),
184+
list: list.New(),
185+
capacity: initialCapacity,
186+
}
187+
}
188+
189+
func (c *SimpleCache) Get(key string) interface{} {
190+
c.mu.Lock()
191+
defer c.mu.Unlock()
192+
if elem, ok := c.data[key]; ok {
193+
item := elem.Value.(*cacheItem)
194+
c.list.MoveToFront(elem)
195+
return item.value
196+
}
197+
return nil
198+
}
199+
140200
func (c *Cache) Get(key string) interface{} {
141201
c.mu.Lock()
142202
defer c.mu.Unlock()
@@ -148,6 +208,27 @@ func (c *Cache) Get(key string) interface{} {
148208
return nil
149209
}
150210

211+
func (c *SimpleCache) Set(key string, value interface{}) {
212+
c.mu.Lock()
213+
defer c.mu.Unlock()
214+
215+
if e, ok := c.data[key]; ok {
216+
item := e.Value.(*cacheItem)
217+
item.value = value
218+
c.list.MoveToFront(e)
219+
} else {
220+
elem := c.list.PushFront(&cacheItem{key: key, value: value})
221+
c.data[key] = elem
222+
if c.capacity >= 0 && len(c.data) > c.capacity {
223+
last := c.list.Back()
224+
item := last.Value.(*cacheItem)
225+
c.list.Remove(last)
226+
delete(c.data, item.key)
227+
}
228+
}
229+
return
230+
}
231+
151232
func (c *Cache) Set(key string, value interface{}) {
152233
c.mu.Lock()
153234
defer c.checkAndFlush()
@@ -179,6 +260,18 @@ func (c *Cache) Set(key string, value interface{}) {
179260
return
180261
}
181262

263+
func (c *SimpleCache) Delete(key string) interface{} {
264+
c.mu.Lock()
265+
defer c.mu.Unlock()
266+
267+
if elem, ok := c.data[key]; ok {
268+
delete(c.data, key)
269+
item := elem.Value.(*cacheItem)
270+
return item.value
271+
}
272+
return nil
273+
}
274+
182275
func (c *Cache) Delete(key string) interface{} {
183276
c.mu.Lock()
184277
defer c.mu.Unlock()

cache_test.go

+76-13
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
*
1616
*/
1717

18-
package cache
18+
package cache2
1919

2020
import (
21+
"sync"
2122
"testing"
2223
"time"
2324
)
@@ -29,7 +30,7 @@ func TestInsertValues(t *testing.T) {
2930
kv["key3"] = "3"
3031
kv["key4"] = "4"
3132

32-
c := New(5, 5, 0*time.Second, nil)
33+
c := NewSimple(5)
3334

3435
for k, v := range kv {
3536
c.Set(k, v)
@@ -58,7 +59,7 @@ func TestExceedCapacityValues(t *testing.T) {
5859

5960
keys := []string{"key1", "key2", "key3", "key4"}
6061

61-
c := New(3, 5, 0*time.Second, nil)
62+
c := NewSimple(3)
6263

6364
for _, k := range keys {
6465
c.Set(k, kv[k])
@@ -78,7 +79,7 @@ func TestUpdateValue(t *testing.T) {
7879
kv["key3"] = "3"
7980
kv["key4"] = "4"
8081

81-
c := New(5, 5, 0*time.Second, nil)
82+
c := NewSimple(5)
8283

8384
for k, v := range kv {
8485
c.Set(k, v)
@@ -98,7 +99,7 @@ func TestDeleteValue(t *testing.T) {
9899
kv["key3"] = "3"
99100
kv["key4"] = "4"
100101

101-
c := New(5, 5, 0*time.Second, nil)
102+
c := NewSimple(5)
102103

103104
for k, v := range kv {
104105
c.Set(k, v)
@@ -113,6 +114,7 @@ func TestDeleteValue(t *testing.T) {
113114

114115
type memFlusher struct {
115116
data map[string]interface{}
117+
m sync.Mutex
116118
}
117119

118120
func newMemFlusher() *memFlusher {
@@ -122,13 +124,25 @@ func newMemFlusher() *memFlusher {
122124
}
123125

124126
func (f *memFlusher) Add(key string, value interface{}) {
127+
f.m.Lock()
128+
defer f.m.Unlock()
125129
f.data[key] = value
126130
}
127131

128132
func (f *memFlusher) Remove(key string) {
133+
f.m.Lock()
134+
defer f.m.Unlock()
129135
delete(f.data, key)
130136
}
131137

138+
// threadSafeGet checks if Add() was the most recently called function for key on this flusher. Needed for `go test -race`
139+
func (f *memFlusher) threadSafeGet(key string) (interface{}, bool) {
140+
f.m.Lock()
141+
defer f.m.Unlock()
142+
value, ok := f.data[key]
143+
return value, ok
144+
}
145+
132146
func TestFlushOnDirty(t *testing.T) {
133147
kv := make(map[string]string)
134148
kv["key1"] = "1"
@@ -146,7 +160,7 @@ func TestFlushOnDirty(t *testing.T) {
146160
}
147161

148162
for _, k := range keys[:3] {
149-
if _, ok := f.data[k]; !ok {
163+
if _, ok := f.threadSafeGet(k); !ok {
150164
t.Errorf("%v does not exist", k)
151165
}
152166
}
@@ -172,7 +186,7 @@ func TestFlushOnTimeOut(t *testing.T) {
172186
time.Sleep(duration + 1*time.Second)
173187

174188
for _, k := range keys {
175-
if _, ok := f.data[k]; !ok {
189+
if _, ok := f.threadSafeGet(k); !ok {
176190
t.Errorf("%v does not exist", k)
177191
}
178192
}
@@ -203,29 +217,78 @@ func TestEvictValue(t *testing.T) {
203217

204218
}
205219

220+
func expectCachedValueEquals(t *testing.T, c CacheInterface, k string, expectedValue string) {
221+
value := c.Get(k)
222+
if value != expectedValue {
223+
t.Errorf("should be %v on key %v. Got %v",
224+
expectedValue, k, value)
225+
}
226+
}
227+
206228
func TestAlwaysInMemoryCache(t *testing.T) {
207229
kv := make(map[string]string)
208230
kv["key1"] = "1"
209231
kv["key2"] = "2"
210232
kv["key3"] = "3"
211233
kv["key4"] = "4"
212234

213-
c := New(-1, -1, 0*time.Second, nil)
235+
c := NewSimple(-1)
214236

215237
for k, v := range kv {
216238
c.Set(k, v)
217239
}
218240

219241
for k, v := range kv {
220-
value := c.Get(k).(string)
221-
if value != v {
222-
t.Errorf("should be %v on key %v. Got %v",
223-
v, k, value)
224-
}
242+
expectCachedValueEquals(t, c, k, v)
225243
}
226244

227245
value := c.Get("notexist")
228246
if value != nil {
229247
t.Errorf("Got nonexist")
230248
}
231249
}
250+
251+
func testEvictsOldValuesHelper(t *testing.T, flusher Flusher, timeout time.Duration) {
252+
kv := make(map[string]string)
253+
kv["key1"] = "1"
254+
kv["key2"] = "2"
255+
kv["key3"] = "3"
256+
// keyList is used because go maps are unordered.
257+
keyList := []string{"key1", "key2", "key3"}
258+
259+
var c CacheInterface
260+
if flusher != nil {
261+
c = New(3, 0, timeout, flusher)
262+
} else {
263+
c = NewSimple(3)
264+
}
265+
266+
for _, k := range keyList {
267+
c.Set(k, kv[k])
268+
}
269+
270+
for _, k := range keyList {
271+
expectCachedValueEquals(t, c, k, kv[k])
272+
}
273+
274+
c.Set("key4", "4")
275+
if v := c.Get("key1"); v != nil {
276+
t.Errorf("Got %v for key1, expected least recently accessed value to be evicted", v)
277+
}
278+
expectCachedValueEquals(t, c, "key4", "4")
279+
expectCachedValueEquals(t, c, "key3", "3")
280+
expectCachedValueEquals(t, c, "key2", "2")
281+
c.Set("key5", "5")
282+
expectCachedValueEquals(t, c, "key5", "5")
283+
if v := c.Get("key4"); v != nil {
284+
t.Errorf("Got %v for key4, expected least recently accessed value to be evicted", v)
285+
}
286+
}
287+
288+
func TestSimpleCacheEvictsOldValues(t *testing.T) {
289+
testEvictsOldValuesHelper(t, nil, 0*time.Second)
290+
}
291+
292+
func TestFlushingCacheEvictsOldValues(t *testing.T) {
293+
testEvictsOldValuesHelper(t, newMemFlusher(), 0*time.Second)
294+
}

0 commit comments

Comments
 (0)