Compare commits
12 commits
Author | SHA1 | Date | |
---|---|---|---|
|
4f264cc4f1 | ||
|
818c60532e | ||
|
1189f7f993 | ||
|
839a17bedb | ||
|
0dbf3f125f | ||
|
f3b2b9fd88 | ||
|
aa0e37ad6f | ||
|
df91803297 | ||
|
a42bd4a9c8 | ||
|
e9b7be5016 | ||
|
fdd08e71c4 | ||
|
992cd9564b |
14 changed files with 115 additions and 70 deletions
10
bucket.go
10
bucket.go
|
@ -23,9 +23,9 @@ func (b *bucket) get(key string) *Item {
|
||||||
return b.lookup[key]
|
return b.lookup[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bucket) set(key string, value interface{}, duration time.Duration) (*Item, *Item) {
|
func (b *bucket) set(key string, value interface{}, duration time.Duration, track bool) (*Item, *Item) {
|
||||||
expires := time.Now().Add(duration).UnixNano()
|
expires := time.Now().Add(duration).UnixNano()
|
||||||
item := newItem(key, value, expires)
|
item := newItem(key, value, expires, track)
|
||||||
b.Lock()
|
b.Lock()
|
||||||
existing := b.lookup[key]
|
existing := b.lookup[key]
|
||||||
b.lookup[key] = item
|
b.lookup[key] = item
|
||||||
|
@ -54,9 +54,9 @@ func (b *bucket) delete(key string) *Item {
|
||||||
// the item from the map. I'm pretty sure this is 100% fine, but it is unique.
|
// 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
|
// (We do this so that the write to the channel is under the read lock and not the
|
||||||
// write lock)
|
// write lock)
|
||||||
func (b *bucket) deleteFunc(matches func(key string, item interface{}) bool, deletables chan *Item) int {
|
func (b *bucket) deleteFunc(matches func(key string, item *Item) bool, deletables chan *Item) int {
|
||||||
lookup := b.lookup
|
lookup := b.lookup
|
||||||
items := make([]*Item, 0, len(lookup)/10)
|
items := make([]*Item, 0)
|
||||||
|
|
||||||
b.RLock()
|
b.RLock()
|
||||||
for key, item := range lookup {
|
for key, item := range lookup {
|
||||||
|
@ -81,7 +81,7 @@ func (b *bucket) deleteFunc(matches func(key string, item interface{}) bool, del
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bucket) deletePrefix(prefix string, deletables chan *Item) int {
|
func (b *bucket) deletePrefix(prefix string, deletables chan *Item) int {
|
||||||
return b.deleteFunc(func(key string, item interface{}) bool {
|
return b.deleteFunc(func(key string, item *Item) bool {
|
||||||
return strings.HasPrefix(key, prefix)
|
return strings.HasPrefix(key, prefix)
|
||||||
}, deletables)
|
}, deletables)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package ccache
|
package ccache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/karlseguin/expect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
. "github.com/karlseguin/expect"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BucketTests struct {
|
type BucketTests struct {
|
||||||
|
@ -32,7 +33,7 @@ func (_ *BucketTests) DeleteItemFromBucket() {
|
||||||
|
|
||||||
func (_ *BucketTests) SetsANewBucketItem() {
|
func (_ *BucketTests) SetsANewBucketItem() {
|
||||||
bucket := testBucket()
|
bucket := testBucket()
|
||||||
item, existing := bucket.set("spice", TestValue("flow"), time.Minute)
|
item, existing := bucket.set("spice", TestValue("flow"), time.Minute, false)
|
||||||
assertValue(item, "flow")
|
assertValue(item, "flow")
|
||||||
item = bucket.get("spice")
|
item = bucket.get("spice")
|
||||||
assertValue(item, "flow")
|
assertValue(item, "flow")
|
||||||
|
@ -41,7 +42,7 @@ func (_ *BucketTests) SetsANewBucketItem() {
|
||||||
|
|
||||||
func (_ *BucketTests) SetsAnExistingItem() {
|
func (_ *BucketTests) SetsAnExistingItem() {
|
||||||
bucket := testBucket()
|
bucket := testBucket()
|
||||||
item, existing := bucket.set("power", TestValue("9001"), time.Minute)
|
item, existing := bucket.set("power", TestValue("9001"), time.Minute, false)
|
||||||
assertValue(item, "9001")
|
assertValue(item, "9001")
|
||||||
item = bucket.get("power")
|
item = bucket.get("power")
|
||||||
assertValue(item, "9001")
|
assertValue(item, "9001")
|
||||||
|
|
41
cache.go
41
cache.go
|
@ -17,6 +17,10 @@ type setMaxSize struct {
|
||||||
size int64
|
size int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type clear struct {
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
*Configuration
|
*Configuration
|
||||||
list *list.List
|
list *list.List
|
||||||
|
@ -55,6 +59,10 @@ func (c *Cache) ItemCount() int {
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Size() int64 {
|
||||||
|
return c.size
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cache) DeletePrefix(prefix string) int {
|
func (c *Cache) DeletePrefix(prefix string) int {
|
||||||
count := 0
|
count := 0
|
||||||
for _, b := range c.buckets {
|
for _, b := range c.buckets {
|
||||||
|
@ -64,7 +72,7 @@ func (c *Cache) DeletePrefix(prefix string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deletes all items that the matches func evaluates to true.
|
// Deletes all items that the matches func evaluates to true.
|
||||||
func (c *Cache) DeleteFunc(matches func(key string, item interface{}) bool) int {
|
func (c *Cache) DeleteFunc(matches func(key string, item *Item) bool) int {
|
||||||
count := 0
|
count := 0
|
||||||
for _, b := range c.buckets {
|
for _, b := range c.buckets {
|
||||||
count += b.deleteFunc(matches, c.deletables)
|
count += b.deleteFunc(matches, c.deletables)
|
||||||
|
@ -98,9 +106,15 @@ func (c *Cache) TrackingGet(key string) TrackedItem {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used when the cache was created with the Track() configuration option.
|
||||||
|
// Sets the item, and returns a tracked reference to it.
|
||||||
|
func (c *Cache) TrackingSet(key string, value interface{}, duration time.Duration) TrackedItem {
|
||||||
|
return c.set(key, value, duration, true)
|
||||||
|
}
|
||||||
|
|
||||||
// Set the value in the cache for the specified duration
|
// Set the value in the cache for the specified duration
|
||||||
func (c *Cache) Set(key string, value interface{}, duration time.Duration) {
|
func (c *Cache) Set(key string, value interface{}, duration time.Duration) {
|
||||||
c.set(key, value, duration)
|
c.set(key, value, duration, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the value if it exists, does not set if it doesn't.
|
// Replace the value if it exists, does not set if it doesn't.
|
||||||
|
@ -127,7 +141,7 @@ func (c *Cache) Fetch(key string, duration time.Duration, fetch func() (interfac
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c.set(key, value, duration), nil
|
return c.set(key, value, duration, false), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the item from the cache, return true if the item was present, false otherwise.
|
// Remove the item from the cache, return true if the item was present, false otherwise.
|
||||||
|
@ -140,13 +154,11 @@ func (c *Cache) Delete(key string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
//this isn't thread safe. It's meant to be called from non-concurrent tests
|
// Clears the cache
|
||||||
func (c *Cache) Clear() {
|
func (c *Cache) Clear() {
|
||||||
for _, bucket := range c.buckets {
|
done := make(chan struct{})
|
||||||
bucket.clear()
|
c.control <- clear{done: done}
|
||||||
}
|
<-done
|
||||||
c.size = 0
|
|
||||||
c.list = list.New()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stops the background worker. Operations performed on the cache after Stop
|
// Stops the background worker. Operations performed on the cache after Stop
|
||||||
|
@ -182,8 +194,8 @@ func (c *Cache) deleteItem(bucket *bucket, item *Item) {
|
||||||
c.deletables <- item
|
c.deletables <- item
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) set(key string, value interface{}, duration time.Duration) *Item {
|
func (c *Cache) set(key string, value interface{}, duration time.Duration, track bool) *Item {
|
||||||
item, existing := c.bucket(key).set(key, value, duration)
|
item, existing := c.bucket(key).set(key, value, duration, track)
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
c.deletables <- existing
|
c.deletables <- existing
|
||||||
}
|
}
|
||||||
|
@ -225,6 +237,13 @@ func (c *Cache) worker() {
|
||||||
if c.size > c.maxSize {
|
if c.size > c.maxSize {
|
||||||
dropped += c.gc()
|
dropped += c.gc()
|
||||||
}
|
}
|
||||||
|
case clear:
|
||||||
|
for _, bucket := range c.buckets {
|
||||||
|
bucket.clear()
|
||||||
|
}
|
||||||
|
c.size = 0
|
||||||
|
c.list = list.New()
|
||||||
|
msg.done <- struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ccache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -63,17 +64,17 @@ func (_ CacheTests) DeletesAFunc() {
|
||||||
cache.Set("f", 6, time.Minute)
|
cache.Set("f", 6, time.Minute)
|
||||||
Expect(cache.ItemCount()).To.Equal(6)
|
Expect(cache.ItemCount()).To.Equal(6)
|
||||||
|
|
||||||
Expect(cache.DeleteFunc(func(key string, item interface{}) bool {
|
Expect(cache.DeleteFunc(func(key string, item *Item) bool {
|
||||||
return false
|
return false
|
||||||
})).To.Equal(0)
|
})).To.Equal(0)
|
||||||
Expect(cache.ItemCount()).To.Equal(6)
|
Expect(cache.ItemCount()).To.Equal(6)
|
||||||
|
|
||||||
Expect(cache.DeleteFunc(func(key string, item interface{}) bool {
|
Expect(cache.DeleteFunc(func(key string, item *Item) bool {
|
||||||
return item.(*Item).Value().(int) < 4
|
return item.Value().(int) < 4
|
||||||
})).To.Equal(3)
|
})).To.Equal(3)
|
||||||
Expect(cache.ItemCount()).To.Equal(3)
|
Expect(cache.ItemCount()).To.Equal(3)
|
||||||
|
|
||||||
Expect(cache.DeleteFunc(func(key string, item interface{}) bool {
|
Expect(cache.DeleteFunc(func(key string, item *Item) bool {
|
||||||
return key == "d"
|
return key == "d"
|
||||||
})).To.Equal(1)
|
})).To.Equal(1)
|
||||||
Expect(cache.ItemCount()).To.Equal(2)
|
Expect(cache.ItemCount()).To.Equal(2)
|
||||||
|
@ -81,10 +82,10 @@ func (_ CacheTests) DeletesAFunc() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ CacheTests) OnDeleteCallbackCalled() {
|
func (_ CacheTests) OnDeleteCallbackCalled() {
|
||||||
onDeleteFnCalled := false
|
onDeleteFnCalled := int32(0)
|
||||||
onDeleteFn := func(item *Item) {
|
onDeleteFn := func(item *Item) {
|
||||||
if item.key == "spice" {
|
if item.key == "spice" {
|
||||||
onDeleteFnCalled = true
|
atomic.AddInt32(&onDeleteFnCalled, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +99,7 @@ func (_ CacheTests) OnDeleteCallbackCalled() {
|
||||||
|
|
||||||
Expect(cache.Get("spice")).To.Equal(nil)
|
Expect(cache.Get("spice")).To.Equal(nil)
|
||||||
Expect(cache.Get("worm").Value()).To.Equal("sand")
|
Expect(cache.Get("worm").Value()).To.Equal("sand")
|
||||||
Expect(onDeleteFnCalled).To.Equal(true)
|
Expect(atomic.LoadInt32(&onDeleteFnCalled)).To.Eql(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ CacheTests) FetchesExpiredItems() {
|
func (_ CacheTests) FetchesExpiredItems() {
|
||||||
|
@ -140,18 +141,21 @@ func (_ CacheTests) PromotedItemsDontGetPruned() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ CacheTests) TrackerDoesNotCleanupHeldInstance() {
|
func (_ CacheTests) TrackerDoesNotCleanupHeldInstance() {
|
||||||
cache := New(Configure().ItemsToPrune(10).Track())
|
cache := New(Configure().ItemsToPrune(11).Track())
|
||||||
for i := 0; i < 10; i++ {
|
item0 := cache.TrackingSet("0", 0, time.Minute)
|
||||||
|
for i := 1; i < 11; i++ {
|
||||||
cache.Set(strconv.Itoa(i), i, time.Minute)
|
cache.Set(strconv.Itoa(i), i, time.Minute)
|
||||||
}
|
}
|
||||||
item := cache.TrackingGet("0")
|
item1 := cache.TrackingGet("1")
|
||||||
time.Sleep(time.Millisecond * 10)
|
time.Sleep(time.Millisecond * 10)
|
||||||
gcCache(cache)
|
gcCache(cache)
|
||||||
Expect(cache.Get("0").Value()).To.Equal(0)
|
Expect(cache.Get("0").Value()).To.Equal(0)
|
||||||
Expect(cache.Get("1")).To.Equal(nil)
|
Expect(cache.Get("1").Value()).To.Equal(1)
|
||||||
item.Release()
|
item0.Release()
|
||||||
|
item1.Release()
|
||||||
gcCache(cache)
|
gcCache(cache)
|
||||||
Expect(cache.Get("0")).To.Equal(nil)
|
Expect(cache.Get("0")).To.Equal(nil)
|
||||||
|
Expect(cache.Get("1")).To.Equal(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ CacheTests) RemovesOldestItemWhenFull() {
|
func (_ CacheTests) RemovesOldestItemWhenFull() {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package ccache
|
package ccache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/karlseguin/expect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/karlseguin/expect"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigurationTests struct{}
|
type ConfigurationTests struct{}
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -1,8 +1,8 @@
|
||||||
module github.com/karlseguin/ccache/v2
|
module github.com/lbryio/ccache/v2
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003
|
github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003
|
||||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 // indirect
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -1,5 +1,3 @@
|
||||||
github.com/karlseguin/expect v1.0.1 h1:z4wy4npwwHSWKjGWH85WNJO42VQhovxTCZDSzhjo8hY=
|
|
||||||
github.com/karlseguin/expect v1.0.1/go.mod h1:zNBxMY8P21owkeogJELCLeHIt+voOSduHYTFUbwRAV8=
|
|
||||||
github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003 h1:vJ0Snvo+SLMY72r5J4sEfkuE7AFbixEP2qRbEcum/wA=
|
github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003 h1:vJ0Snvo+SLMY72r5J4sEfkuE7AFbixEP2qRbEcum/wA=
|
||||||
github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003/go.mod h1:zNBxMY8P21owkeogJELCLeHIt+voOSduHYTFUbwRAV8=
|
github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003/go.mod h1:zNBxMY8P21owkeogJELCLeHIt+voOSduHYTFUbwRAV8=
|
||||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
|
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
|
||||||
|
|
8
item.go
8
item.go
|
@ -52,18 +52,22 @@ type Item struct {
|
||||||
element *list.Element
|
element *list.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
func newItem(key string, value interface{}, expires int64) *Item {
|
func newItem(key string, value interface{}, expires int64, track bool) *Item {
|
||||||
size := int64(1)
|
size := int64(1)
|
||||||
if sized, ok := value.(Sized); ok {
|
if sized, ok := value.(Sized); ok {
|
||||||
size = sized.Size()
|
size = sized.Size()
|
||||||
}
|
}
|
||||||
return &Item{
|
item := &Item{
|
||||||
key: key,
|
key: key,
|
||||||
value: value,
|
value: value,
|
||||||
promotions: 0,
|
promotions: 0,
|
||||||
size: size,
|
size: size,
|
||||||
expires: expires,
|
expires: expires,
|
||||||
}
|
}
|
||||||
|
if track {
|
||||||
|
item.refCount = 1
|
||||||
|
}
|
||||||
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Item) shouldPromote(getsPerPromote int32) bool {
|
func (i *Item) shouldPromote(getsPerPromote int32) bool {
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (b *layeredBucket) getSecondaryBucket(primary string) *bucket {
|
||||||
return bucket
|
return bucket
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *layeredBucket) set(primary, secondary string, value interface{}, duration time.Duration) (*Item, *Item) {
|
func (b *layeredBucket) set(primary, secondary string, value interface{}, duration time.Duration, track bool) (*Item, *Item) {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
bkt, exists := b.buckets[primary]
|
bkt, exists := b.buckets[primary]
|
||||||
if exists == false {
|
if exists == false {
|
||||||
|
@ -46,7 +46,7 @@ func (b *layeredBucket) set(primary, secondary string, value interface{}, durati
|
||||||
b.buckets[primary] = bkt
|
b.buckets[primary] = bkt
|
||||||
}
|
}
|
||||||
b.Unlock()
|
b.Unlock()
|
||||||
item, existing := bkt.set(secondary, value, duration)
|
item, existing := bkt.set(secondary, value, duration, track)
|
||||||
item.group = primary
|
item.group = primary
|
||||||
return item, existing
|
return item, existing
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ func (b *layeredBucket) deletePrefix(primary, prefix string, deletables chan *It
|
||||||
return bucket.deletePrefix(prefix, deletables)
|
return bucket.deletePrefix(prefix, deletables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *layeredBucket) deleteFunc(primary string, matches func(key string, item interface{}) bool, deletables chan *Item) int {
|
func (b *layeredBucket) deleteFunc(primary string, matches func(key string, item *Item) bool, deletables chan *Item) int {
|
||||||
b.RLock()
|
b.RLock()
|
||||||
bucket, exists := b.buckets[primary]
|
bucket, exists := b.buckets[primary]
|
||||||
b.RUnlock()
|
b.RUnlock()
|
||||||
|
|
|
@ -102,9 +102,14 @@ func (c *LayeredCache) TrackingGet(primary, secondary string) TrackedItem {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the value in the cache for the specified duration
|
||||||
|
func (c *LayeredCache) TrackingSet(primary, secondary string, value interface{}, duration time.Duration) TrackedItem {
|
||||||
|
return c.set(primary, secondary, value, duration, true)
|
||||||
|
}
|
||||||
|
|
||||||
// Set the value in the cache for the specified duration
|
// Set the value in the cache for the specified duration
|
||||||
func (c *LayeredCache) Set(primary, secondary string, value interface{}, duration time.Duration) {
|
func (c *LayeredCache) Set(primary, secondary string, value interface{}, duration time.Duration) {
|
||||||
c.set(primary, secondary, value, duration)
|
c.set(primary, secondary, value, duration, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the value if it exists, does not set if it doesn't.
|
// Replace the value if it exists, does not set if it doesn't.
|
||||||
|
@ -131,7 +136,7 @@ func (c *LayeredCache) Fetch(primary, secondary string, duration time.Duration,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c.set(primary, secondary, value, duration), nil
|
return c.set(primary, secondary, value, duration, false), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the item from the cache, return true if the item was present, false otherwise.
|
// Remove the item from the cache, return true if the item was present, false otherwise.
|
||||||
|
@ -155,17 +160,15 @@ func (c *LayeredCache) DeletePrefix(primary, prefix string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deletes all items that share the same primary key and where the matches func evaluates to true.
|
// Deletes all items that share the same primary key and where the matches func evaluates to true.
|
||||||
func (c *LayeredCache) DeleteFunc(primary string, matches func(key string, item interface{}) bool) int {
|
func (c *LayeredCache) DeleteFunc(primary string, matches func(key string, item *Item) bool) int {
|
||||||
return c.bucket(primary).deleteFunc(primary, matches, c.deletables)
|
return c.bucket(primary).deleteFunc(primary, matches, c.deletables)
|
||||||
}
|
}
|
||||||
|
|
||||||
//this isn't thread safe. It's meant to be called from non-concurrent tests
|
// Clears the cache
|
||||||
func (c *LayeredCache) Clear() {
|
func (c *LayeredCache) Clear() {
|
||||||
for _, bucket := range c.buckets {
|
done := make(chan struct{})
|
||||||
bucket.clear()
|
c.control <- clear{done: done}
|
||||||
}
|
<-done
|
||||||
c.size = 0
|
|
||||||
c.list = list.New()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LayeredCache) Stop() {
|
func (c *LayeredCache) Stop() {
|
||||||
|
@ -193,8 +196,8 @@ func (c *LayeredCache) restart() {
|
||||||
go c.worker()
|
go c.worker()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LayeredCache) set(primary, secondary string, value interface{}, duration time.Duration) *Item {
|
func (c *LayeredCache) set(primary, secondary string, value interface{}, duration time.Duration, track bool) *Item {
|
||||||
item, existing := c.bucket(primary).set(primary, secondary, value, duration)
|
item, existing := c.bucket(primary).set(primary, secondary, value, duration, track)
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
c.deletables <- existing
|
c.deletables <- existing
|
||||||
}
|
}
|
||||||
|
@ -244,6 +247,13 @@ func (c *LayeredCache) worker() {
|
||||||
if c.size > c.maxSize {
|
if c.size > c.maxSize {
|
||||||
dropped += c.gc()
|
dropped += c.gc()
|
||||||
}
|
}
|
||||||
|
case clear:
|
||||||
|
for _, bucket := range c.buckets {
|
||||||
|
bucket.clear()
|
||||||
|
}
|
||||||
|
c.size = 0
|
||||||
|
c.list = list.New()
|
||||||
|
msg.done <- struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ccache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -107,17 +108,17 @@ func (_ *LayeredCacheTests) DeletesAFunc() {
|
||||||
cache.Set("spice", "f", 6, time.Minute)
|
cache.Set("spice", "f", 6, time.Minute)
|
||||||
Expect(cache.ItemCount()).To.Equal(6)
|
Expect(cache.ItemCount()).To.Equal(6)
|
||||||
|
|
||||||
Expect(cache.DeleteFunc("spice", func(key string, item interface{}) bool {
|
Expect(cache.DeleteFunc("spice", func(key string, item *Item) bool {
|
||||||
return false
|
return false
|
||||||
})).To.Equal(0)
|
})).To.Equal(0)
|
||||||
Expect(cache.ItemCount()).To.Equal(6)
|
Expect(cache.ItemCount()).To.Equal(6)
|
||||||
|
|
||||||
Expect(cache.DeleteFunc("spice", func(key string, item interface{}) bool {
|
Expect(cache.DeleteFunc("spice", func(key string, item *Item) bool {
|
||||||
return item.(*Item).Value().(int) < 4
|
return item.Value().(int) < 4
|
||||||
})).To.Equal(2)
|
})).To.Equal(2)
|
||||||
Expect(cache.ItemCount()).To.Equal(4)
|
Expect(cache.ItemCount()).To.Equal(4)
|
||||||
|
|
||||||
Expect(cache.DeleteFunc("spice", func(key string, item interface{}) bool {
|
Expect(cache.DeleteFunc("spice", func(key string, item *Item) bool {
|
||||||
return key == "d"
|
return key == "d"
|
||||||
})).To.Equal(1)
|
})).To.Equal(1)
|
||||||
Expect(cache.ItemCount()).To.Equal(3)
|
Expect(cache.ItemCount()).To.Equal(3)
|
||||||
|
@ -125,12 +126,10 @@ func (_ *LayeredCacheTests) DeletesAFunc() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ *LayeredCacheTests) OnDeleteCallbackCalled() {
|
func (_ *LayeredCacheTests) OnDeleteCallbackCalled() {
|
||||||
|
onDeleteFnCalled := int32(0)
|
||||||
onDeleteFnCalled := false
|
|
||||||
onDeleteFn := func(item *Item) {
|
onDeleteFn := func(item *Item) {
|
||||||
|
|
||||||
if item.group == "spice" && item.key == "flow" {
|
if item.group == "spice" && item.key == "flow" {
|
||||||
onDeleteFnCalled = true
|
atomic.AddInt32(&onDeleteFnCalled, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +147,7 @@ func (_ *LayeredCacheTests) OnDeleteCallbackCalled() {
|
||||||
Expect(cache.Get("spice", "worm")).To.Equal(nil)
|
Expect(cache.Get("spice", "worm")).To.Equal(nil)
|
||||||
Expect(cache.Get("leto", "sister").Value()).To.Equal("ghanima")
|
Expect(cache.Get("leto", "sister").Value()).To.Equal("ghanima")
|
||||||
|
|
||||||
Expect(onDeleteFnCalled).To.Equal(true)
|
Expect(atomic.LoadInt32(&onDeleteFnCalled)).To.Eql(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ *LayeredCacheTests) DeletesALayer() {
|
func (_ *LayeredCacheTests) DeletesALayer() {
|
||||||
|
@ -196,17 +195,20 @@ func (_ LayeredCacheTests) PromotedItemsDontGetPruned() {
|
||||||
|
|
||||||
func (_ LayeredCacheTests) TrackerDoesNotCleanupHeldInstance() {
|
func (_ LayeredCacheTests) TrackerDoesNotCleanupHeldInstance() {
|
||||||
cache := Layered(Configure().ItemsToPrune(10).Track())
|
cache := Layered(Configure().ItemsToPrune(10).Track())
|
||||||
for i := 0; i < 10; i++ {
|
item0 := cache.TrackingSet("0", "a", 0, time.Minute)
|
||||||
|
for i := 1; i < 11; i++ {
|
||||||
cache.Set(strconv.Itoa(i), "a", i, time.Minute)
|
cache.Set(strconv.Itoa(i), "a", i, time.Minute)
|
||||||
}
|
}
|
||||||
item := cache.TrackingGet("0", "a")
|
item1 := cache.TrackingGet("1", "a")
|
||||||
time.Sleep(time.Millisecond * 10)
|
time.Sleep(time.Millisecond * 10)
|
||||||
gcLayeredCache(cache)
|
gcLayeredCache(cache)
|
||||||
Expect(cache.Get("0", "a").Value()).To.Equal(0)
|
Expect(cache.Get("0", "a").Value()).To.Equal(0)
|
||||||
Expect(cache.Get("1", "a")).To.Equal(nil)
|
Expect(cache.Get("1", "a").Value()).To.Equal(1)
|
||||||
item.Release()
|
item0.Release()
|
||||||
|
item1.Release()
|
||||||
gcLayeredCache(cache)
|
gcLayeredCache(cache)
|
||||||
Expect(cache.Get("0", "a")).To.Equal(nil)
|
Expect(cache.Get("0", "a")).To.Equal(nil)
|
||||||
|
Expect(cache.Get("1", "a")).To.Equal(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ LayeredCacheTests) RemovesOldestItemWhenFull() {
|
func (_ LayeredCacheTests) RemovesOldestItemWhenFull() {
|
||||||
|
@ -260,7 +262,9 @@ func (_ LayeredCacheTests) ResizeOnTheFly() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLayered() *LayeredCache {
|
func newLayered() *LayeredCache {
|
||||||
return Layered(Configure())
|
c := Layered(Configure())
|
||||||
|
c.Clear()
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ LayeredCacheTests) RemovesOldestItemWhenFullBySizer() {
|
func (_ LayeredCacheTests) RemovesOldestItemWhenFullBySizer() {
|
||||||
|
|
|
@ -96,8 +96,11 @@ cache.Delete("user:4")
|
||||||
### DeletePrefix
|
### DeletePrefix
|
||||||
`DeletePrefix` deletes all keys matching the provided prefix. Returns the number of keys removed.
|
`DeletePrefix` deletes all keys matching the provided prefix. Returns the number of keys removed.
|
||||||
|
|
||||||
|
### DeleteFunc
|
||||||
|
`DeleteFunc` deletes all items that the provded matches func evaluates to true. Returns the number of keys removed.
|
||||||
|
|
||||||
### Clear
|
### Clear
|
||||||
`Clear` clears the cache. This method is **not** thread safe. It is meant to be used from tests.
|
`Clear` clears the cache. If the cache's gc is running, `Clear` waits for it to finish.
|
||||||
|
|
||||||
### Extend
|
### Extend
|
||||||
The life of an item can be changed via the `Extend` method. This will change the expiry of the item by the specified duration relative to the current time.
|
The life of an item can be changed via the `Extend` method. This will change the expiry of the item by the specified duration relative to the current time.
|
||||||
|
@ -140,7 +143,7 @@ user := item.Value() //will be nil if "user:4" didn't exist in the cache
|
||||||
item.Release() //can be called even if item.Value() returned nil
|
item.Release() //can be called even if item.Value() returned nil
|
||||||
```
|
```
|
||||||
|
|
||||||
In practice, `Release` wouldn't be called until later, at some other place in your code.
|
In practice, `Release` wouldn't be called until later, at some other place in your code. `TrackingSet` can be used to set a value to be tracked.
|
||||||
|
|
||||||
There's a couple reason to use the tracking mode if other parts of your code also hold references to objects. First, if you're already going to hold a reference to these objects, there's really no reason not to have them in the cache - the memory is used up anyways.
|
There's a couple reason to use the tracking mode if other parts of your code also hold references to objects. First, if you're already going to hold a reference to these objects, there's really no reason not to have them in the cache - the memory is used up anyways.
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ func (s *SecondaryCache) Get(secondary string) *Item {
|
||||||
// Set the secondary key to a value.
|
// Set the secondary key to a value.
|
||||||
// The semantics are the same as for LayeredCache.Set
|
// The semantics are the same as for LayeredCache.Set
|
||||||
func (s *SecondaryCache) Set(secondary string, value interface{}, duration time.Duration) *Item {
|
func (s *SecondaryCache) Set(secondary string, value interface{}, duration time.Duration) *Item {
|
||||||
item, existing := s.bucket.set(secondary, value, duration)
|
item, existing := s.bucket.set(secondary, value, duration, false)
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
s.pCache.deletables <- existing
|
s.pCache.deletables <- existing
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package ccache
|
package ccache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/karlseguin/expect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
. "github.com/karlseguin/expect"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SecondaryCacheTests struct{}
|
type SecondaryCacheTests struct{}
|
||||||
|
|
Loading…
Reference in a new issue