Skip to content

Commit e1b130b

Browse files
committed
Document the blocking behaviour, make Len nonblocking
1 parent a3474ed commit e1b130b

File tree

2 files changed

+52
-53
lines changed

2 files changed

+52
-53
lines changed

cache.go

+33-33
Original file line numberDiff line numberDiff line change
@@ -136,32 +136,6 @@ func (c *Cache) Get(key interface{}) (value interface{}, closer io.Closer, exist
136136
}
137137
}
138138

139-
// Pop evicts and returns the oldest key and value. If LFU ordering is
140-
// enabled, then the least frequently used key and value.
141-
//
142-
// Pop is a non-blocking operation.
143-
func (c *Cache) Pop() (key, value interface{}) {
144-
c.mu.Lock()
145-
defer c.mu.Unlock()
146-
if key = c.list.Pop(); key == nil {
147-
return nil, nil
148-
}
149-
value, ok := c.evictLocked(key)
150-
if !ok {
151-
panic("evcache: invalid map state")
152-
}
153-
return key, value
154-
}
155-
156-
// Evict evicts a key and returns its value.
157-
//
158-
// Evict is a non-blocking operation.
159-
func (c *Cache) Evict(key interface{}) (interface{}, bool) {
160-
c.mu.Lock()
161-
defer c.mu.Unlock()
162-
return c.evictLocked(key)
163-
}
164-
165139
// Range calls f for each key and value present in the cache in no particular order.
166140
// If f returns false, Range stops the iteration.
167141
//
@@ -189,7 +163,7 @@ func (c *Cache) Range(f func(key, value interface{}) bool) {
189163

190164
// Flush evicts all keys from the cache.
191165
//
192-
// Flush skips keys whose fetch callback is currently running.
166+
// Flush is a non-blocking operation.
193167
func (c *Cache) Flush() {
194168
c.records.Range(func(key, _ interface{}) bool {
195169
c.Evict(key)
@@ -201,9 +175,33 @@ func (c *Cache) Flush() {
201175
//
202176
// Len does not block.
203177
func (c *Cache) Len() int {
178+
return c.list.Len()
179+
}
180+
181+
// Pop evicts and returns the oldest key and value. If LFU ordering is
182+
// enabled, then the least frequently used key and value.
183+
//
184+
// Pop may block if a concurrent Do is running.
185+
func (c *Cache) Pop() (key, value interface{}) {
204186
c.mu.Lock()
205187
defer c.mu.Unlock()
206-
return c.list.Len()
188+
if key = c.list.Pop(); key == nil {
189+
return nil, nil
190+
}
191+
value, ok := c.evictLocked(key)
192+
if !ok {
193+
panic("evcache: invalid map state")
194+
}
195+
return key, value
196+
}
197+
198+
// Evict evicts a key and returns its value.
199+
//
200+
// Evict may block if a concurrent Do is running.
201+
func (c *Cache) Evict(key interface{}) (interface{}, bool) {
202+
c.mu.Lock()
203+
defer c.mu.Unlock()
204+
return c.evictLocked(key)
207205
}
208206

209207
// Set the value in the cache for a key.
@@ -213,7 +211,8 @@ func (c *Cache) Len() int {
213211
// depending on whether LFU ordering is enabled or not.
214212
//
215213
// Set may block until a concurrent Fetch callback has returned
216-
// and then immediately overwrite the value.
214+
// and then immediately overwrite the value. It may also block
215+
// if a concurrent Do is running.
217216
func (c *Cache) Set(key, value interface{}, ttl time.Duration) {
218217
var front interface{}
219218
defer func() {
@@ -256,9 +255,10 @@ func (c *Cache) Set(key, value interface{}, ttl time.Duration) {
256255
// the least frequently used record or the eldest is evicted
257256
// depending on whether LFU ordering is enabled or not.
258257
//
259-
// Fetch may block until a concurrent Fetch callback has returned and will return
260-
// that new value or continues with fetching a new key if the concurrent
261-
// callback returned an error.
258+
// Fetch may block until a concurrent Fetch callback for key has returned and will return
259+
// that new value or continues with fetching a new value if the concurrent callback
260+
// returned an error. It may also block if a concurrent Do is running and the value
261+
// did not exist causing a store. If the value exists, Fetch will not block.
262262
func (c *Cache) Fetch(key interface{}, ttl time.Duration, f FetchCallback) (value interface{}, closer io.Closer, err error) {
263263
var front interface{}
264264
defer func() {
@@ -309,7 +309,7 @@ func (c *Cache) Fetch(key interface{}, ttl time.Duration, f FetchCallback) (valu
309309
// from least to most frequently used. If f returns false,
310310
// Do stops the iteration.
311311
//
312-
// Do blocks Pop, Set, Evict and Fetch (only if it stores a value).
312+
// Do blocks Pop, Evict, Set and Fetch (only if it stores a value).
313313
// It does not visit keys whose Fetch callback is currently running.
314314
// f is not allowed to modify the cache.
315315
func (c *Cache) Do(f func(key, value interface{}) bool) {

list.go

+19-20
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package evcache
22

33
import (
44
"container/ring"
5+
"sync/atomic"
56
)
67

78
// ringList is a size-limited list using rings.
@@ -21,38 +22,45 @@ func newRingList(capacity uint32) *ringList {
2122

2223
// Len returns the length of elements in the ring.
2324
func (l *ringList) Len() int {
24-
return int(l.size)
25-
}
26-
27-
// Pop removes and returns the front element.
28-
func (l *ringList) Pop() (value interface{}) {
29-
if l.back == nil {
30-
return nil
31-
}
32-
return l.unlink(l.back.Next())
25+
return int(atomic.LoadUint32(&l.size))
3326
}
3427

3528
// PushBack inserts a value at the back of list. If capacity is exceeded,
3629
// an element from the front of list is removed and its value returned.
3730
func (l *ringList) PushBack(value interface{}, r *ring.Ring) (front interface{}) {
3831
r.Value = value
39-
l.link(r)
40-
if l.capacity > 0 && l.size > l.capacity {
32+
if l.back != nil {
33+
l.back.Link(r)
34+
}
35+
l.back = r
36+
size := atomic.LoadUint32(&l.size)
37+
if l.capacity > 0 && size+1 > l.capacity {
4138
front = l.unlink(l.back.Next())
4239
if front == value {
4340
panic("evcache: front cannot be value")
4441
}
4542
return front
4643
}
44+
atomic.AddUint32(&l.size, 1)
4745
return nil
4846
}
4947

48+
// Pop removes and returns the front element.
49+
func (l *ringList) Pop() (value interface{}) {
50+
if l.back == nil {
51+
return nil
52+
}
53+
atomic.AddUint32(&l.size, ^uint32(0))
54+
return l.unlink(l.back.Next())
55+
}
56+
5057
// Remove an element from the list.
5158
func (l *ringList) Remove(r *ring.Ring) (value interface{}) {
5259
if r.Value == nil {
5360
// An overflowed element unlinked by PushBack.
5461
return nil
5562
}
63+
atomic.AddUint32(&l.size, ^uint32(0))
5664
return l.unlink(r)
5765
}
5866

@@ -83,14 +91,6 @@ func (l *ringList) Do(f func(value interface{}) bool) {
8391
}
8492
}
8593

86-
func (l *ringList) link(r *ring.Ring) {
87-
if l.back != nil {
88-
l.back.Link(r)
89-
}
90-
l.back = r
91-
l.size++
92-
}
93-
9494
func (l *ringList) unlink(r *ring.Ring) (key interface{}) {
9595
if l.back == nil {
9696
panic("evcache: invalid cursor")
@@ -104,7 +104,6 @@ func (l *ringList) unlink(r *ring.Ring) (key interface{}) {
104104
l.back = r.Prev()
105105
}
106106
r.Prev().Unlink(1)
107-
l.size--
108107
key = r.Value
109108
r.Value = nil
110109
return key

0 commit comments

Comments
 (0)