commit
78289f8f0b
4 changed files with 77 additions and 3 deletions
46
bucket.go
46
bucket.go
|
@ -1,6 +1,7 @@
|
|||
package ccache
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
@ -26,22 +27,61 @@ func (b *bucket) set(key string, value interface{}, duration time.Duration) (*It
|
|||
expires := time.Now().Add(duration).UnixNano()
|
||||
item := newItem(key, value, expires)
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
existing := b.lookup[key]
|
||||
b.lookup[key] = item
|
||||
b.Unlock()
|
||||
return item, existing
|
||||
}
|
||||
|
||||
func (b *bucket) delete(key string) *Item {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
item := b.lookup[key]
|
||||
delete(b.lookup, key)
|
||||
b.Unlock()
|
||||
return item
|
||||
}
|
||||
|
||||
// This is an expensive operation, so we do what we can to optimize it and limit
|
||||
// the impact it has on concurrent operations. Specifically, we:
|
||||
// 1 - Do an initial iteration to collect matches. This allows us to do the
|
||||
// "expensive" prefix check (on all values) using only a read-lock
|
||||
// 2 - Do a second iteration, under write lock, for the matched results to do
|
||||
// the actual deletion
|
||||
|
||||
// Also, this is the only place where the Bucket is aware of cache detail: the
|
||||
// deletables channel. Passing it here lets us avoid iterating over matched items
|
||||
// again in the cache. Further, we pass item to deletables BEFORE actually removing
|
||||
// the item from the map. I'm pretty sure this is 100% fine, but it is unique.
|
||||
// (We do this so that the write to the channel is under the read lock and not the
|
||||
// write lock)
|
||||
func (b *bucket) deletePrefix(prefix string, deletables chan *Item) int {
|
||||
lookup := b.lookup
|
||||
items := make([]*Item, 0, len(lookup)/10)
|
||||
|
||||
b.RLock()
|
||||
for key, item := range lookup {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
deletables <- item
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
b.RUnlock()
|
||||
|
||||
if len(items) == 0 {
|
||||
// avoid the write lock if we can
|
||||
return 0
|
||||
}
|
||||
|
||||
b.Lock()
|
||||
for _, item := range items {
|
||||
delete(lookup, item.key)
|
||||
}
|
||||
b.Unlock()
|
||||
return len(items)
|
||||
}
|
||||
|
||||
func (b *bucket) clear() {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
b.lookup = make(map[string]*Item)
|
||||
b.Unlock()
|
||||
}
|
||||
|
|
8
cache.go
8
cache.go
|
@ -45,6 +45,14 @@ func (c *Cache) ItemCount() int {
|
|||
return count
|
||||
}
|
||||
|
||||
func (c *Cache) DeletePrefix(prefix string) int {
|
||||
count := 0
|
||||
for _, b := range c.buckets {
|
||||
count += b.deletePrefix(prefix, c.deletables)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Get an item from the cache. Returns nil if the item wasn't found.
|
||||
// This can return an expired item. Use item.Expired() to see if the item
|
||||
// is expired and item.TTL() to see how long until the item expires (which
|
||||
|
|
|
@ -28,6 +28,29 @@ func (_ CacheTests) DeletesAValue() {
|
|||
Expect(cache.ItemCount()).To.Equal(1)
|
||||
}
|
||||
|
||||
func (_ CacheTests) DeletesAPrefix() {
|
||||
cache := New(Configure())
|
||||
Expect(cache.ItemCount()).To.Equal(0)
|
||||
|
||||
cache.Set("aaa", "1", time.Minute)
|
||||
cache.Set("aab", "2", time.Minute)
|
||||
cache.Set("aac", "3", time.Minute)
|
||||
cache.Set("ac", "4", time.Minute)
|
||||
cache.Set("z5", "7", time.Minute)
|
||||
Expect(cache.ItemCount()).To.Equal(5)
|
||||
|
||||
Expect(cache.DeletePrefix("9a")).To.Equal(0)
|
||||
Expect(cache.ItemCount()).To.Equal(5)
|
||||
|
||||
Expect(cache.DeletePrefix("aa")).To.Equal(3)
|
||||
Expect(cache.Get("aaa")).To.Equal(nil)
|
||||
Expect(cache.Get("aab")).To.Equal(nil)
|
||||
Expect(cache.Get("aac")).To.Equal(nil)
|
||||
Expect(cache.Get("ac").Value()).To.Equal("4")
|
||||
Expect(cache.Get("z5").Value()).To.Equal("7")
|
||||
Expect(cache.ItemCount()).To.Equal(2)
|
||||
}
|
||||
|
||||
func (_ CacheTests) OnDeleteCallbackCalled() {
|
||||
onDeleteFnCalled := false
|
||||
onDeleteFn := func(item *Item) {
|
||||
|
|
|
@ -91,6 +91,9 @@ item, err := cache.Fetch("user:4", time.Minute * 10, func() (interface{}, error)
|
|||
cache.Delete("user:4")
|
||||
```
|
||||
|
||||
### DeletePrefix
|
||||
`DeletePrefix` deletes all keys matching the provided prefix. Returns the number of keys removed.
|
||||
|
||||
### Clear
|
||||
`Clear` clears the cache. This method is **not** thread safe. It is meant to be used from tests.
|
||||
|
||||
|
|
Loading…
Reference in a new issue