Various updates to LRU functionality
This commit is contained in:
commit
fdfec335d5
@ -5,4 +5,5 @@ code was contributed.)
|
|||||||
|
|
||||||
Dustin Sallings <dustin@spy.net>
|
Dustin Sallings <dustin@spy.net>
|
||||||
Jason Mooberry <jasonmoo@me.com>
|
Jason Mooberry <jasonmoo@me.com>
|
||||||
|
Matthew Keller <m@cognusion.com>
|
||||||
Sergey Shepelev <temotor@gmail.com>
|
Sergey Shepelev <temotor@gmail.com>
|
||||||
|
@ -13,6 +13,15 @@ cache can be saved to and loaded from a file (using `c.Items()` to retrieve the
|
|||||||
items map to serialize, and `NewFrom()` to create a cache from a deserialized
|
items map to serialize, and `NewFrom()` to create a cache from a deserialized
|
||||||
one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats.)
|
one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats.)
|
||||||
|
|
||||||
|
When creating a cache object using `NewWithLRU()`, if you set the maxItems value
|
||||||
|
above 0, the LRU functionality is enabled. The cache automatically updates a
|
||||||
|
timestamp every time a given item is retrieved. In the background, the janitor takes
|
||||||
|
care of deleting items that have expired because of staleness, or are
|
||||||
|
least-recently-used when the cache is under pressure. Whatever you set your purge
|
||||||
|
interval to controls when the item will actually be removed from the cache. If you
|
||||||
|
don't want to use the janitor, and wish to manually purge LRU items, then
|
||||||
|
`c.DeleteLRU(n)` where `n` is the number of items you'd like to purge.
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
`go get github.com/patrickmn/go-cache`
|
`go get github.com/patrickmn/go-cache`
|
||||||
|
310
cache.go
310
cache.go
@ -13,6 +13,7 @@ import (
|
|||||||
type Item struct {
|
type Item struct {
|
||||||
Object interface{}
|
Object interface{}
|
||||||
Expiration int64
|
Expiration int64
|
||||||
|
Accessed int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the item has expired.
|
// Returns true if the item has expired.
|
||||||
@ -23,6 +24,12 @@ func (item Item) Expired() bool {
|
|||||||
return time.Now().UnixNano() > item.Expiration
|
return time.Now().UnixNano() > item.Expiration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the time at which this item was last accessed.
|
||||||
|
func (item Item) LastAccessed() *time.Time {
|
||||||
|
t := time.Unix(0, item.Accessed)
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// For use with functions that take an expiration time.
|
// For use with functions that take an expiration time.
|
||||||
NoExpiration time.Duration = -1
|
NoExpiration time.Duration = -1
|
||||||
@ -43,6 +50,7 @@ type cache struct {
|
|||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
onEvicted func(string, interface{})
|
onEvicted func(string, interface{})
|
||||||
janitor *janitor
|
janitor *janitor
|
||||||
|
maxItems int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an item to the cache, replacing any existing item. If the duration is 0
|
// Add an item to the cache, replacing any existing item. If the duration is 0
|
||||||
@ -50,34 +58,68 @@ type cache struct {
|
|||||||
// (NoExpiration), the item never expires.
|
// (NoExpiration), the item never expires.
|
||||||
func (c *cache) Set(k string, x interface{}, d time.Duration) {
|
func (c *cache) Set(k string, x interface{}, d time.Duration) {
|
||||||
// "Inlining" of set
|
// "Inlining" of set
|
||||||
var e int64
|
var (
|
||||||
|
now time.Time
|
||||||
|
e int64
|
||||||
|
)
|
||||||
if d == DefaultExpiration {
|
if d == DefaultExpiration {
|
||||||
d = c.defaultExpiration
|
d = c.defaultExpiration
|
||||||
}
|
}
|
||||||
if d > 0 {
|
if d > 0 {
|
||||||
e = time.Now().Add(d).UnixNano()
|
now = time.Now()
|
||||||
|
e = now.Add(d).UnixNano()
|
||||||
}
|
}
|
||||||
c.mu.Lock()
|
if c.maxItems > 0 {
|
||||||
c.items[k] = Item{
|
if d <= 0 {
|
||||||
Object: x,
|
// d <= 0 means we didn't set now above
|
||||||
Expiration: e,
|
now = time.Now()
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
c.items[k] = Item{
|
||||||
|
Object: x,
|
||||||
|
Expiration: e,
|
||||||
|
Accessed: now.UnixNano(),
|
||||||
|
}
|
||||||
|
// TODO: Calls to mu.Unlock are currently not deferred because
|
||||||
|
// defer adds ~200 ns (as of go1.)
|
||||||
|
c.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.items[k] = Item{
|
||||||
|
Object: x,
|
||||||
|
Expiration: e,
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
}
|
}
|
||||||
// TODO: Calls to mu.Unlock are currently not deferred because defer
|
|
||||||
// adds ~200 ns (as of go1.)
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cache) set(k string, x interface{}, d time.Duration) {
|
func (c *cache) set(k string, x interface{}, d time.Duration) {
|
||||||
var e int64
|
var (
|
||||||
|
now time.Time
|
||||||
|
e int64
|
||||||
|
)
|
||||||
if d == DefaultExpiration {
|
if d == DefaultExpiration {
|
||||||
d = c.defaultExpiration
|
d = c.defaultExpiration
|
||||||
}
|
}
|
||||||
if d > 0 {
|
if d > 0 {
|
||||||
e = time.Now().Add(d).UnixNano()
|
now = time.Now()
|
||||||
|
e = now.Add(d).UnixNano()
|
||||||
}
|
}
|
||||||
c.items[k] = Item{
|
if c.maxItems > 0 {
|
||||||
Object: x,
|
if d <= 0 {
|
||||||
Expiration: e,
|
// d <= 0 means we didn't set now above
|
||||||
|
now = time.Now()
|
||||||
|
}
|
||||||
|
c.items[k] = Item{
|
||||||
|
Object: x,
|
||||||
|
Expiration: e,
|
||||||
|
Accessed: now.UnixNano(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.items[k] = Item{
|
||||||
|
Object: x,
|
||||||
|
Expiration: e,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,23 +160,43 @@ func (c *cache) Replace(k string, x interface{}, d time.Duration) error {
|
|||||||
// Get an item from the cache. Returns the item or nil, and a bool indicating
|
// Get an item from the cache. Returns the item or nil, and a bool indicating
|
||||||
// whether the key was found.
|
// whether the key was found.
|
||||||
func (c *cache) Get(k string) (interface{}, bool) {
|
func (c *cache) Get(k string) (interface{}, bool) {
|
||||||
c.mu.RLock()
|
if c.maxItems > 0 {
|
||||||
|
// LRU enabled; Get implies write
|
||||||
|
c.mu.Lock()
|
||||||
|
} else {
|
||||||
|
// LRU not enabled; Get is read-only
|
||||||
|
c.mu.RLock()
|
||||||
|
}
|
||||||
// "Inlining" of get and Expired
|
// "Inlining" of get and Expired
|
||||||
item, found := c.items[k]
|
item, found := c.items[k]
|
||||||
if !found {
|
if !found {
|
||||||
c.mu.RUnlock()
|
if c.maxItems > 0 {
|
||||||
|
c.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
if item.Expiration > 0 {
|
if item.Expiration > 0 {
|
||||||
if time.Now().UnixNano() > item.Expiration {
|
if time.Now().UnixNano() > item.Expiration {
|
||||||
c.mu.RUnlock()
|
if c.maxItems > 0 {
|
||||||
|
c.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.mu.RUnlock()
|
if c.maxItems > 0 {
|
||||||
|
c.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
}
|
||||||
return item.Object, true
|
return item.Object, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If LRU functionality is being used (and get implies updating item.Accessed,)
|
||||||
|
// this function must be write-locked.
|
||||||
func (c *cache) get(k string) (interface{}, bool) {
|
func (c *cache) get(k string) (interface{}, bool) {
|
||||||
item, found := c.items[k]
|
item, found := c.items[k]
|
||||||
if !found {
|
if !found {
|
||||||
@ -146,6 +208,10 @@ func (c *cache) get(k string) (interface{}, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
item.Accessed = time.Now().UnixNano()
|
||||||
|
c.items[k] = item
|
||||||
|
}
|
||||||
return item.Object, true
|
return item.Object, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +227,9 @@ func (c *cache) Increment(k string, n int64) error {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return fmt.Errorf("Item %s not found", k)
|
return fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
switch v.Object.(type) {
|
switch v.Object.(type) {
|
||||||
case int:
|
case int:
|
||||||
v.Object = v.Object.(int) + int(n)
|
v.Object = v.Object.(int) + int(n)
|
||||||
@ -209,6 +278,9 @@ func (c *cache) IncrementFloat(k string, n float64) error {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return fmt.Errorf("Item %s not found", k)
|
return fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
switch v.Object.(type) {
|
switch v.Object.(type) {
|
||||||
case float32:
|
case float32:
|
||||||
v.Object = v.Object.(float32) + float32(n)
|
v.Object = v.Object.(float32) + float32(n)
|
||||||
@ -233,6 +305,9 @@ func (c *cache) IncrementInt(k string, n int) (int, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int)
|
rv, ok := v.Object.(int)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -255,6 +330,9 @@ func (c *cache) IncrementInt8(k string, n int8) (int8, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int8)
|
rv, ok := v.Object.(int8)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -277,6 +355,9 @@ func (c *cache) IncrementInt16(k string, n int16) (int16, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int16)
|
rv, ok := v.Object.(int16)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -299,6 +380,9 @@ func (c *cache) IncrementInt32(k string, n int32) (int32, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int32)
|
rv, ok := v.Object.(int32)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -321,6 +405,9 @@ func (c *cache) IncrementInt64(k string, n int64) (int64, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int64)
|
rv, ok := v.Object.(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -343,6 +430,9 @@ func (c *cache) IncrementUint(k string, n uint) (uint, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint)
|
rv, ok := v.Object.(uint)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -365,6 +455,9 @@ func (c *cache) IncrementUintptr(k string, n uintptr) (uintptr, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uintptr)
|
rv, ok := v.Object.(uintptr)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -387,6 +480,9 @@ func (c *cache) IncrementUint8(k string, n uint8) (uint8, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint8)
|
rv, ok := v.Object.(uint8)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -409,6 +505,9 @@ func (c *cache) IncrementUint16(k string, n uint16) (uint16, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint16)
|
rv, ok := v.Object.(uint16)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -431,6 +530,9 @@ func (c *cache) IncrementUint32(k string, n uint32) (uint32, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint32)
|
rv, ok := v.Object.(uint32)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -453,6 +555,9 @@ func (c *cache) IncrementUint64(k string, n uint64) (uint64, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint64)
|
rv, ok := v.Object.(uint64)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -475,6 +580,9 @@ func (c *cache) IncrementFloat32(k string, n float32) (float32, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(float32)
|
rv, ok := v.Object.(float32)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -497,6 +605,9 @@ func (c *cache) IncrementFloat64(k string, n float64) (float64, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(float64)
|
rv, ok := v.Object.(float64)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -523,6 +634,9 @@ func (c *cache) Decrement(k string, n int64) error {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return fmt.Errorf("Item not found")
|
return fmt.Errorf("Item not found")
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
switch v.Object.(type) {
|
switch v.Object.(type) {
|
||||||
case int:
|
case int:
|
||||||
v.Object = v.Object.(int) - int(n)
|
v.Object = v.Object.(int) - int(n)
|
||||||
@ -571,6 +685,9 @@ func (c *cache) DecrementFloat(k string, n float64) error {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return fmt.Errorf("Item %s not found", k)
|
return fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
switch v.Object.(type) {
|
switch v.Object.(type) {
|
||||||
case float32:
|
case float32:
|
||||||
v.Object = v.Object.(float32) - float32(n)
|
v.Object = v.Object.(float32) - float32(n)
|
||||||
@ -595,6 +712,9 @@ func (c *cache) DecrementInt(k string, n int) (int, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int)
|
rv, ok := v.Object.(int)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -617,6 +737,9 @@ func (c *cache) DecrementInt8(k string, n int8) (int8, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int8)
|
rv, ok := v.Object.(int8)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -639,6 +762,9 @@ func (c *cache) DecrementInt16(k string, n int16) (int16, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int16)
|
rv, ok := v.Object.(int16)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -661,6 +787,9 @@ func (c *cache) DecrementInt32(k string, n int32) (int32, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int32)
|
rv, ok := v.Object.(int32)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -683,6 +812,9 @@ func (c *cache) DecrementInt64(k string, n int64) (int64, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(int64)
|
rv, ok := v.Object.(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -705,6 +837,9 @@ func (c *cache) DecrementUint(k string, n uint) (uint, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint)
|
rv, ok := v.Object.(uint)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -727,6 +862,9 @@ func (c *cache) DecrementUintptr(k string, n uintptr) (uintptr, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uintptr)
|
rv, ok := v.Object.(uintptr)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -749,6 +887,9 @@ func (c *cache) DecrementUint8(k string, n uint8) (uint8, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint8)
|
rv, ok := v.Object.(uint8)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -771,6 +912,9 @@ func (c *cache) DecrementUint16(k string, n uint16) (uint16, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint16)
|
rv, ok := v.Object.(uint16)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -793,6 +937,9 @@ func (c *cache) DecrementUint32(k string, n uint32) (uint32, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint32)
|
rv, ok := v.Object.(uint32)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -815,6 +962,9 @@ func (c *cache) DecrementUint64(k string, n uint64) (uint64, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(uint64)
|
rv, ok := v.Object.(uint64)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -837,6 +987,9 @@ func (c *cache) DecrementFloat32(k string, n float32) (float32, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(float32)
|
rv, ok := v.Object.(float32)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -859,6 +1012,9 @@ func (c *cache) DecrementFloat64(k string, n float64) (float64, error) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return 0, fmt.Errorf("Item %s not found", k)
|
return 0, fmt.Errorf("Item %s not found", k)
|
||||||
}
|
}
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
v.Accessed = time.Now().UnixNano()
|
||||||
|
}
|
||||||
rv, ok := v.Object.(float64)
|
rv, ok := v.Object.(float64)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@ -875,9 +1031,10 @@ func (c *cache) DecrementFloat64(k string, n float64) (float64, error) {
|
|||||||
func (c *cache) Delete(k string) {
|
func (c *cache) Delete(k string) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
v, evicted := c.delete(k)
|
v, evicted := c.delete(k)
|
||||||
|
evictFunc := c.onEvicted
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
if evicted {
|
if evicted {
|
||||||
c.onEvicted(k, v)
|
evictFunc(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -902,8 +1059,9 @@ func (c *cache) DeleteExpired() {
|
|||||||
var evictedItems []keyAndValue
|
var evictedItems []keyAndValue
|
||||||
now := time.Now().UnixNano()
|
now := time.Now().UnixNano()
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
evictFunc := c.onEvicted
|
||||||
for k, v := range c.items {
|
for k, v := range c.items {
|
||||||
// "Inlining" of expired
|
// "Inlining" of Expired
|
||||||
if v.Expiration > 0 && now > v.Expiration {
|
if v.Expiration > 0 && now > v.Expiration {
|
||||||
ov, evicted := c.delete(k)
|
ov, evicted := c.delete(k)
|
||||||
if evicted {
|
if evicted {
|
||||||
@ -913,7 +1071,7 @@ func (c *cache) DeleteExpired() {
|
|||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
for _, v := range evictedItems {
|
for _, v := range evictedItems {
|
||||||
c.onEvicted(v.key, v.value)
|
evictFunc(v.key, v.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -926,6 +1084,74 @@ func (c *cache) OnEvicted(f func(string, interface{})) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete some of the oldest items in the cache if the soft size limit has been
|
||||||
|
// exceeded.
|
||||||
|
func (c *cache) DeleteLRU() {
|
||||||
|
c.mu.Lock()
|
||||||
|
var (
|
||||||
|
overCount = c.itemCount() - c.maxItems
|
||||||
|
evicted []keyAndValue
|
||||||
|
evictFunc = c.onEvicted
|
||||||
|
)
|
||||||
|
evicted = c.deleteLRUAmount(overCount)
|
||||||
|
c.mu.Unlock()
|
||||||
|
for _, v := range evicted {
|
||||||
|
evictFunc(v.key, v.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a number of the oldest items from the cache.
|
||||||
|
func (c *cache) DeleteLRUAmount(numItems int) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.deleteLRUAmount(numItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cache) deleteLRUAmount(numItems int) []keyAndValue {
|
||||||
|
if numItems <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
lastTime int64 = 0
|
||||||
|
lastItems = make([]string, numItems) // Ring buffer
|
||||||
|
liCount = 0
|
||||||
|
full = false
|
||||||
|
evictedItems = make([]keyAndValue, 0, numItems)
|
||||||
|
now = time.Now().UnixNano()
|
||||||
|
)
|
||||||
|
for k, v := range c.items {
|
||||||
|
// "Inlining" of !Expired
|
||||||
|
if v.Expiration == 0 || now <= v.Expiration {
|
||||||
|
if full == false || v.Accessed < lastTime {
|
||||||
|
// We found a least-recently-used item, or our
|
||||||
|
// purge buffer isn't full yet
|
||||||
|
lastTime = v.Accessed
|
||||||
|
// Append it to the buffer, or start overwriting
|
||||||
|
// it
|
||||||
|
if liCount < numItems {
|
||||||
|
lastItems[liCount] = k
|
||||||
|
liCount++
|
||||||
|
} else {
|
||||||
|
lastItems[0] = k
|
||||||
|
liCount = 1
|
||||||
|
full = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lastTime > 0 {
|
||||||
|
for _, v := range lastItems {
|
||||||
|
if v != "" {
|
||||||
|
ov, evicted := c.delete(v)
|
||||||
|
if evicted {
|
||||||
|
evictedItems = append(evictedItems, keyAndValue{v, ov})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return evictedItems
|
||||||
|
}
|
||||||
|
|
||||||
// Write the cache's items (using Gob) to an io.Writer.
|
// Write the cache's items (using Gob) to an io.Writer.
|
||||||
//
|
//
|
||||||
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
|
// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
|
||||||
@ -1031,6 +1257,14 @@ func (c *cache) ItemCount() int {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the number of items in the cache without locking. This may include
|
||||||
|
// items that have expired, but have not yet been cleaned up. Equivalent to
|
||||||
|
// len(c.Items()).
|
||||||
|
func (c *cache) itemCount() int {
|
||||||
|
n := len(c.items)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
// Delete all items from the cache.
|
// Delete all items from the cache.
|
||||||
func (c *cache) Flush() {
|
func (c *cache) Flush() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@ -1050,6 +1284,9 @@ func (j *janitor) Run(c *cache) {
|
|||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
c.DeleteExpired()
|
c.DeleteExpired()
|
||||||
|
if c.maxItems > 0 {
|
||||||
|
c.DeleteLRU()
|
||||||
|
}
|
||||||
case <-j.stop:
|
case <-j.stop:
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
return
|
return
|
||||||
@ -1069,19 +1306,20 @@ func runJanitor(c *cache, ci time.Duration) {
|
|||||||
go j.Run(c)
|
go j.Run(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCache(de time.Duration, m map[string]Item) *cache {
|
func newCache(de time.Duration, m map[string]Item, mi int) *cache {
|
||||||
if de == 0 {
|
if de == 0 {
|
||||||
de = -1
|
de = -1
|
||||||
}
|
}
|
||||||
c := &cache{
|
c := &cache{
|
||||||
defaultExpiration: de,
|
defaultExpiration: de,
|
||||||
|
maxItems: mi,
|
||||||
items: m,
|
items: m,
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
|
func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item, mi int) *Cache {
|
||||||
c := newCache(de, m)
|
c := newCache(de, m, mi)
|
||||||
// This trick ensures that the janitor goroutine (which--granted it
|
// This trick ensures that the janitor goroutine (which--granted it
|
||||||
// was enabled--is running DeleteExpired on c forever) does not keep
|
// was enabled--is running DeleteExpired on c forever) does not keep
|
||||||
// the returned C object from being garbage collected. When it is
|
// the returned C object from being garbage collected. When it is
|
||||||
@ -1102,7 +1340,22 @@ func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item)
|
|||||||
// deleted from the cache before calling c.DeleteExpired().
|
// deleted from the cache before calling c.DeleteExpired().
|
||||||
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
|
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
|
||||||
items := make(map[string]Item)
|
items := make(map[string]Item)
|
||||||
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
|
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a new cache with a given default expiration duration, cleanup
|
||||||
|
// interval, and maximum-ish number of items. If the expiration duration
|
||||||
|
// is less than one (or NoExpiration), the items in the cache never expire
|
||||||
|
// (by default), and must be deleted manually. If the cleanup interval is
|
||||||
|
// less than one, expired items are not deleted from the cache before
|
||||||
|
// calling c.DeleteExpired(), c.DeleteLRU(), or c.DeleteLRUAmount(). If maxItems
|
||||||
|
// is not greater than zero, then there will be no soft cap on the number of
|
||||||
|
// items in the cache.
|
||||||
|
//
|
||||||
|
// Using the LRU functionality makes Get() a slower, write-locked operation.
|
||||||
|
func NewWithLRU(defaultExpiration, cleanupInterval time.Duration, maxItems int) *Cache {
|
||||||
|
items := make(map[string]Item)
|
||||||
|
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items, maxItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a new cache with a given default expiration duration and cleanup
|
// Return a new cache with a given default expiration duration and cleanup
|
||||||
@ -1127,5 +1380,10 @@ func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
|
|||||||
// map retrieved with c.Items(), and to register those same types before
|
// map retrieved with c.Items(), and to register those same types before
|
||||||
// decoding a blob containing an items map.
|
// decoding a blob containing an items map.
|
||||||
func NewFrom(defaultExpiration, cleanupInterval time.Duration, items map[string]Item) *Cache {
|
func NewFrom(defaultExpiration, cleanupInterval time.Duration, items map[string]Item) *Cache {
|
||||||
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
|
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar to NewFrom, but creates a cache with LRU functionality enabled.
|
||||||
|
func NewFromWithLRU(defaultExpiration, cleanupInterval time.Duration, items map[string]Item, maxItems int) *Cache {
|
||||||
|
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items, maxItems)
|
||||||
}
|
}
|
||||||
|
@ -1224,6 +1224,41 @@ func TestDecrementUnderflowUint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Ring buffer is more efficient but doesn't guarantee that the actually
|
||||||
|
// oldest items are removed, just some old items. This shouldn't be significant
|
||||||
|
// for large caches, but we can't test it easily.
|
||||||
|
//
|
||||||
|
// func TestDeleteLRU(t *testing.T) {
|
||||||
|
// tc := NewWithLRU(1*time.Second, 0, 1)
|
||||||
|
// tc.Set("foo", 0, DefaultExpiration)
|
||||||
|
// tc.Set("bar", 1, DefaultExpiration)
|
||||||
|
// tc.Set("baz", 2, DefaultExpiration)
|
||||||
|
// tc.Get("foo")
|
||||||
|
// tc.Get("baz")
|
||||||
|
// time.Sleep(5 * time.Millisecond)
|
||||||
|
// tc.Get("bar")
|
||||||
|
// // Bar was accessed most recently, and should be the only value that
|
||||||
|
// // stays.
|
||||||
|
// tc.DeleteLRU()
|
||||||
|
// if tc.ItemCount() != 1 {
|
||||||
|
// t.Error("tc.ItemCount() is not 1")
|
||||||
|
// }
|
||||||
|
// if _, found := tc.Get("bar"); !found {
|
||||||
|
// t.Error("bar was not found")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func TestDeleteLRU(t *testing.T) {
|
||||||
|
tc := NewWithLRU(1*time.Second, 0, 1)
|
||||||
|
tc.Set("foo", 0, DefaultExpiration)
|
||||||
|
tc.Set("bar", 1, DefaultExpiration)
|
||||||
|
tc.Set("baz", 2, DefaultExpiration)
|
||||||
|
tc.DeleteLRU()
|
||||||
|
if tc.ItemCount() != 1 {
|
||||||
|
t.Error("tc.ItemCount() is not 1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOnEvicted(t *testing.T) {
|
func TestOnEvicted(t *testing.T) {
|
||||||
tc := New(DefaultExpiration, 0)
|
tc := New(DefaultExpiration, 0)
|
||||||
tc.Set("foo", 3, DefaultExpiration)
|
tc.Set("foo", 3, DefaultExpiration)
|
||||||
@ -1443,6 +1478,24 @@ func benchmarkCacheGet(b *testing.B, exp time.Duration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheWithLRUGetExpiring(b *testing.B) {
|
||||||
|
benchmarkCacheWithLRUGet(b, 5*time.Minute, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheWithLRUGetNotExpiring(b *testing.B) {
|
||||||
|
benchmarkCacheWithLRUGet(b, NoExpiration, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkCacheWithLRUGet(b *testing.B, exp time.Duration, max int) {
|
||||||
|
b.StopTimer()
|
||||||
|
tc := NewWithLRU(exp, 0, max)
|
||||||
|
tc.Set("foo", "bar", DefaultExpiration)
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tc.Get("foo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkRWMutexMapGet(b *testing.B) {
|
func BenchmarkRWMutexMapGet(b *testing.B) {
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
m := map[string]string{
|
m := map[string]string{
|
||||||
|
Loading…
Reference in New Issue
Block a user