ccache/layeredcache.go
Karl Seguin 77765a3f11 Get now returns the *Item rather than the item's value. Get no longer actively
purges stale items.

Combining these two changes, CCache can now be used to implement both of
Varnish's grace and saint mode.
2014-10-25 17:15:47 +07:00

163 lines
3.5 KiB
Go

// An LRU cached aimed at high concurrency
package ccache
import (
"container/list"
"hash/fnv"
"sync/atomic"
"time"
)
type LayeredCache struct {
*Configuration
list *list.List
buckets []*LayeredBucket
bucketCount uint32
deletables chan *Item
promotables chan *Item
}
func Layered(config *Configuration) *LayeredCache {
c := &LayeredCache{
list: list.New(),
Configuration: config,
bucketCount: uint32(config.buckets),
buckets: make([]*LayeredBucket, config.buckets),
deletables: make(chan *Item, config.deleteBuffer),
promotables: make(chan *Item, config.promoteBuffer),
}
for i := 0; i < int(config.buckets); i++ {
c.buckets[i] = &LayeredBucket{
buckets: make(map[string]*Bucket),
}
}
go c.worker()
return c
}
func (c *LayeredCache) Get(primary, secondary string) *Item {
bucket := c.bucket(primary)
item := bucket.get(primary, secondary)
if item == nil {
return nil
}
if item.expires > time.Now().Unix() {
c.conditionalPromote(item)
}
return item
}
func (c *LayeredCache) TrackingGet(primary, secondary string) TrackedItem {
item := c.Get(primary, secondary)
if item == nil {
return NilTracked
}
item.track()
return item
}
func (c *LayeredCache) Set(primary, secondary string, value interface{}, duration time.Duration) {
item, new := c.bucket(primary).set(primary, secondary, value, duration)
if new {
c.promote(item)
} else {
c.conditionalPromote(item)
}
}
func (c *LayeredCache) Fetch(primary, secondary string, duration time.Duration, fetch func() (interface{}, error)) (interface{}, error) {
item := c.Get(primary, secondary)
if item != nil {
return item, nil
}
value, err := fetch()
if err == nil {
c.Set(primary, secondary, value, duration)
}
return value, err
}
func (c *LayeredCache) Delete(primary, secondary string) {
item := c.bucket(primary).delete(primary, secondary)
if item != nil {
c.deletables <- item
}
}
func (c *LayeredCache) DeleteAll(primary string) {
c.bucket(primary).deleteAll(primary, c.deletables)
}
//this isn't thread safe. It's meant to be called from non-concurrent tests
func (c *LayeredCache) Clear() {
for _, bucket := range c.buckets {
bucket.clear()
}
c.list = list.New()
}
func (c *LayeredCache) bucket(key string) *LayeredBucket {
h := fnv.New32a()
h.Write([]byte(key))
return c.buckets[h.Sum32()%c.bucketCount]
}
func (c *LayeredCache) conditionalPromote(item *Item) {
if item.shouldPromote(c.getsPerPromote) == false {
return
}
c.promote(item)
}
func (c *LayeredCache) promote(item *Item) {
c.promotables <- item
}
func (c *LayeredCache) worker() {
for {
select {
case item := <-c.promotables:
if c.doPromote(item) && uint64(c.list.Len()) > c.maxItems {
c.gc()
}
case item := <-c.deletables:
if item.element == nil {
item.promotions = -2
} else {
c.list.Remove(item.element)
}
}
}
}
func (c *LayeredCache) doPromote(item *Item) bool {
// deleted before it ever got promoted
if item.promotions == -2 {
return false
}
item.promotions = 0
if item.element != nil { //not a new item
c.list.MoveToFront(item.element)
return false
}
item.element = c.list.PushFront(item)
return true
}
func (c *LayeredCache) gc() {
element := c.list.Back()
for i := 0; i < c.itemsToPrune; i++ {
if element == nil {
return
}
prev := element.Prev()
item := element.Value.(*Item)
if c.tracking == false || atomic.LoadInt32(&item.refCount) == 0 {
c.bucket(item.group).delete(item.group, item.key)
c.list.Remove(element)
}
element = prev
}
}