Skip to content

Commit 15bc2ad

Browse files
committed
update circular buffer
1 parent 1b89ba4 commit 15bc2ad

File tree

2 files changed

+85
-140
lines changed

2 files changed

+85
-140
lines changed

utils/circularbuff/circularbuff.go

Lines changed: 40 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
package circularbuff
22

33
import (
4-
"container/list"
54
"errors"
65
)
76

87
// EvictCallback is used to get a callback when a cache entry is evicted
98
type EvictCallback func(key interface{}, value interface{})
109

10+
type key interface{}
11+
type value interface{}
12+
1113
// Cache implements a non-thread safe fixed size circular cache
1214
// Once max size is reached, every subsequent writing would overwrite oldest element
1315
type Cache struct {
14-
maxSize int
15-
evictList *list.List
16-
items map[interface{}]*list.Element
17-
onEvict EvictCallback
18-
}
19-
20-
// entry is used to hold a value in the evictList
21-
type entry struct {
22-
key interface{}
23-
value interface{}
16+
maxSize int
17+
next int
18+
buf []key
19+
items map[key]value
20+
onEvict EvictCallback
2421
}
2522

2623
// New creates a circular cache of the given size.
@@ -34,118 +31,69 @@ func NewWithEvict(maxSize int, onEvict EvictCallback) (*Cache, error) {
3431
return nil, errors.New("must provide a non-negative size")
3532
}
3633
c := &Cache{
37-
maxSize: maxSize,
38-
evictList: list.New(),
39-
items: make(map[interface{}]*list.Element),
40-
onEvict: onEvict,
34+
maxSize: maxSize,
35+
next: 0,
36+
buf: make([]key, maxSize),
37+
items: make(map[key]value),
38+
onEvict: onEvict,
4139
}
4240
return c, nil
4341
}
4442

4543
// Purge is used to completely clear the cache.
4644
func (c *Cache) Purge() {
47-
for k, v := range c.items {
48-
e := v.Value.(*entry)
49-
if c.onEvict != nil {
50-
c.onEvict(k, e.value)
51-
}
45+
for k, _ := range c.items {
5246
delete(c.items, k)
5347
}
54-
c.evictList.Init()
48+
49+
// reset cache
50+
c.buf = make([]key, c.maxSize)
51+
c.next = 0
5552
}
5653

5754
// Add adds a value to the cache. Returns true if an eviction occurred.
5855
func (c *Cache) Add(key, value interface{}) (evicted bool) {
5956
// Check for existing item
60-
if ent, ok := c.items[key]; ok {
61-
c.evictList.MoveToFront(ent)
62-
existing := ent.Value.(*entry)
63-
existing.value = value
64-
return false
57+
evicted = false
58+
if c.buf[c.next] == key {
59+
v, _ := c.items[key]
60+
if c.onEvict != nil {
61+
c.onEvict(key, v)
62+
evicted = true
63+
}
6564
}
6665

67-
// Add new item
68-
ent := &entry{key, value}
69-
if c.Len() >= c.maxSize {
70-
c.removeOldest()
71-
entry := c.evictList.PushBack(ent)
72-
c.items[key] = entry
73-
return true
74-
}
75-
entry := c.evictList.PushFront(ent)
76-
c.items[key] = entry
77-
return false
66+
delete(c.items, c.buf[c.next]) // delete old key in the map if any
67+
c.buf[c.next] = key
68+
c.items[key] = value
69+
c.next = (c.next + 1) % c.maxSize
70+
return evicted
7871
}
7972

8073
// Get looks up a key's value from the cache.
81-
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
82-
if ent, ok := c.items[key]; ok {
83-
c.evictList.MoveToFront(ent)
84-
if ent.Value.(*entry) == nil {
74+
func (c *Cache) Get(key interface{}) (interface{}, bool) {
75+
if value, ok := c.items[key]; ok {
76+
if value == nil {
8577
return nil, false
8678
}
87-
return ent.Value.(*entry).value, true
79+
return value, ok
8880
}
89-
return
81+
return nil, false
9082
}
9183

9284
// Contains checks if a key is in the cache, without updating the recent-ness
9385
// or deleting it for being stale.
94-
func (c *Cache) Contains(key interface{}) (ok bool) {
95-
_, ok = c.items[key]
86+
func (c *Cache) Contains(key interface{}) bool {
87+
_, ok := c.items[key]
9688
return ok
9789
}
9890

99-
// Peek returns the key value (or undefined if not found) without updating
100-
// the "recently used"-ness of the key.
101-
func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
102-
var ent *list.Element
103-
if ent, ok = c.items[key]; ok {
104-
return ent.Value.(*entry).value, true
105-
}
106-
return nil, ok
107-
}
108-
10991
// Remove removes the provided key from the cache, returning if the
11092
// key was contained.
111-
func (c *Cache) Remove(key interface{}) (present bool) {
112-
if ent, ok := c.items[key]; ok {
113-
c.removeElement(ent)
93+
func (c *Cache) Remove(key interface{}) bool {
94+
if _, ok := c.items[key]; ok {
95+
delete(c.items, key)
11496
return true
11597
}
11698
return false
11799
}
118-
119-
// Keys returns a slice of the keys in the cache, from oldest to newest.
120-
func (c *Cache) Keys() []interface{} {
121-
keys := make([]interface{}, len(c.items))
122-
i := 0
123-
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
124-
keys[i] = ent.Value.(*entry).key
125-
i++
126-
}
127-
return keys
128-
}
129-
130-
// Len returns the number of items in the cache.
131-
func (c *Cache) Len() int {
132-
return c.evictList.Len()
133-
}
134-
135-
// removeOldest removes the oldest item from the cache.
136-
func (c *Cache) removeOldest() {
137-
ent := c.evictList.Back()
138-
if ent != nil {
139-
c.removeElement(ent)
140-
}
141-
}
142-
143-
// removeElement is used to remove a given list element from the cache
144-
func (c *Cache) removeElement(e *list.Element) {
145-
c.evictList.Remove(e)
146-
kv := e.Value.(*entry)
147-
delete(c.items, kv.key)
148-
if c.onEvict != nil {
149-
c.onEvict(kv.key, kv.value)
150-
}
151-
}

utils/circularbuff/circularbuff_test.go

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,68 +7,62 @@ import (
77
)
88

99
func TestCircularCache(t *testing.T) {
10+
cache, err := New(-1)
11+
require.Nil(t, cache)
12+
require.Error(t, err)
13+
1014
size := 10
11-
cache, err := New(size)
15+
cache, err = New(size)
1216
require.NoError(t, err)
1317
// fill up cache
1418
for i := 1; i <= size; i++ {
1519
evicted := cache.Add(i, i)
1620
require.False(t, evicted)
1721
}
18-
require.Equal(t, cache.Len(), size)
19-
back, ok := cache.Peek(1)
20-
require.True(t, ok)
21-
two, ok := cache.Peek(2)
22-
require.True(t, ok)
23-
front, ok := cache.Peek(10)
24-
require.True(t, ok)
25-
_, ok = cache.Peek(11)
26-
require.False(t, ok)
2722

28-
require.Equal(t, back, 1)
29-
require.Equal(t, two, 2)
30-
require.Equal(t, front, 10)
23+
// adding same key on the same position but no evict becuase the evict is nil
24+
require.True(t, cache.Contains(1))
25+
val, _ := cache.Get(1)
26+
require.Equal(t, 1, val)
27+
evicted := cache.Add(1, 11)
28+
require.False(t, evicted)
29+
require.True(t, cache.Contains(1))
30+
val, _ = cache.Get(1)
31+
require.Equal(t, 11, val)
3132

3233
// add one more item when the cache is full
33-
// 11 will replace the oldest item, a.k.a 1
34-
evicted := cache.Add(11, 11)
35-
require.True(t, evicted)
36-
require.Equal(t, cache.Len(), size)
37-
38-
eleven, ok := cache.Peek(11)
39-
require.True(t, ok)
40-
require.Equal(t, eleven, 11)
41-
_, ok = cache.Peek(1)
34+
// 11 will replace the next position, a.k.a 2
35+
require.True(t, cache.Contains(2))
36+
val, ok := cache.Get(11)
37+
require.Nil(t, val)
4238
require.False(t, ok)
39+
evicted = cache.Add(11, 11)
40+
require.False(t, evicted)
41+
require.False(t, cache.Contains(2))
4342

44-
// 12 will replace 11
45-
evicted = cache.Add(12, 12)
46-
require.True(t, evicted)
47-
require.Equal(t, cache.Len(), size)
48-
49-
twelve, ok := cache.Get(12) // 12 is moved to front
50-
require.True(t, ok)
51-
require.Equal(t, twelve, 12)
52-
_, ok = cache.Peek(11)
53-
require.False(t, ok)
43+
// Remove
44+
require.True(t, cache.Contains(3))
45+
require.True(t, cache.Remove(3))
46+
require.False(t, cache.Remove(12))
47+
require.False(t, cache.Contains(3))
5448

55-
// 13 will replace the oldest one, a.k.a 2
56-
evicted = cache.Add(13, 13)
57-
require.True(t, evicted)
58-
require.Equal(t, cache.Len(), size)
49+
// Purge
50+
require.True(t, cache.Contains(6))
51+
cache.Purge()
52+
require.False(t, cache.Contains(6))
5953

60-
_, ok = cache.Peek(2)
54+
// Add/Get nil value
55+
require.False(t, cache.Add(12, nil))
56+
val, ok = cache.Get(12)
57+
require.Nil(t, val)
6158
require.False(t, ok)
62-
63-
cache.Purge()
64-
require.Equal(t, cache.Len(), 0)
6559
}
6660

6761
func TestCircularCacheWithCallback(t *testing.T) {
6862
size := 10
6963
cache, err := NewWithEvict(size, func(key interface{}, value interface{}) {
70-
require.Equal(t, key, 2)
71-
require.Equal(t, value, 2)
64+
require.Equal(t, key, 1)
65+
require.Equal(t, value, 1)
7266
})
7367
require.NoError(t, err)
7468
// fill up cache
@@ -77,13 +71,16 @@ func TestCircularCacheWithCallback(t *testing.T) {
7771
require.False(t, evicted)
7872
}
7973

80-
// re-add 1 to move it from back to front
74+
// re-add 1, evict callback fundtion will be called
75+
// and check that evicted item is 1/1
76+
require.True(t, cache.Contains(1))
8177
evicted := cache.Add(1, 1)
82-
require.False(t, evicted)
78+
require.True(t, evicted)
79+
require.True(t, cache.Contains(1))
8380

84-
// Add one more item, evict callback fundtion will be called
85-
// and check that evicted item is 2/2, the oldest item
81+
// Add one more item into the next position, 2
82+
require.True(t, cache.Contains(2))
8683
evicted = cache.Add(11, 11)
87-
require.True(t, evicted)
88-
require.Equal(t, cache.Len(), size)
84+
require.False(t, evicted)
85+
require.False(t, cache.Contains(2))
8986
}

0 commit comments

Comments
 (0)