preliminary work on DeletePrefix
This commit is contained in:
parent
2ff889bcae
commit
356e164dd5
3 changed files with 71 additions and 0 deletions
40
bucket.go
40
bucket.go
|
@ -1,6 +1,7 @@
|
||||||
package ccache
|
package ccache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -40,6 +41,45 @@ func (b *bucket) delete(key string) *Item {
|
||||||
return item
|
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()
|
||||||
|
defer b.Unlock()
|
||||||
|
for _, item := range items {
|
||||||
|
delete(lookup, item.key)
|
||||||
|
}
|
||||||
|
return len(items)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *bucket) clear() {
|
func (b *bucket) clear() {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
defer b.Unlock()
|
defer b.Unlock()
|
||||||
|
|
8
cache.go
8
cache.go
|
@ -45,6 +45,14 @@ func (c *Cache) ItemCount() int {
|
||||||
return count
|
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.
|
// 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
|
// 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
|
// 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)
|
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() {
|
func (_ CacheTests) OnDeleteCallbackCalled() {
|
||||||
onDeleteFnCalled := false
|
onDeleteFnCalled := false
|
||||||
onDeleteFn := func(item *Item) {
|
onDeleteFn := func(item *Item) {
|
||||||
|
|
Loading…
Reference in a new issue