Various updates to LRU functionality
This commit is contained in:
commit
fdfec335d5
@ -5,4 +5,5 @@ code was contributed.)
|
||||
|
||||
Dustin Sallings <dustin@spy.net>
|
||||
Jason Mooberry <jasonmoo@me.com>
|
||||
Matthew Keller <m@cognusion.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
|
||||
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
|
||||
|
||||
`go get github.com/patrickmn/go-cache`
|
||||
|
310
cache.go
310
cache.go
@ -13,6 +13,7 @@ import (
|
||||
type Item struct {
|
||||
Object interface{}
|
||||
Expiration int64
|
||||
Accessed int64
|
||||
}
|
||||
|
||||
// Returns true if the item has expired.
|
||||
@ -23,6 +24,12 @@ func (item Item) Expired() bool {
|
||||
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 (
|
||||
// For use with functions that take an expiration time.
|
||||
NoExpiration time.Duration = -1
|
||||
@ -43,6 +50,7 @@ type cache struct {
|
||||
mu sync.RWMutex
|
||||
onEvicted func(string, interface{})
|
||||
janitor *janitor
|
||||
maxItems int
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *cache) Set(k string, x interface{}, d time.Duration) {
|
||||
// "Inlining" of set
|
||||
var e int64
|
||||
var (
|
||||
now time.Time
|
||||
e int64
|
||||
)
|
||||
if d == DefaultExpiration {
|
||||
d = c.defaultExpiration
|
||||
}
|
||||
if d > 0 {
|
||||
e = time.Now().Add(d).UnixNano()
|
||||
now = time.Now()
|
||||
e = now.Add(d).UnixNano()
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.items[k] = Item{
|
||||
Object: x,
|
||||
Expiration: e,
|
||||
if c.maxItems > 0 {
|
||||
if d <= 0 {
|
||||
// d <= 0 means we didn't set now above
|
||||
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) {
|
||||
var e int64
|
||||
var (
|
||||
now time.Time
|
||||
e int64
|
||||
)
|
||||
if d == DefaultExpiration {
|
||||
d = c.defaultExpiration
|
||||
}
|
||||
if d > 0 {
|
||||
e = time.Now().Add(d).UnixNano()
|
||||
now = time.Now()
|
||||
e = now.Add(d).UnixNano()
|
||||
}
|
||||
c.items[k] = Item{
|
||||
Object: x,
|
||||
Expiration: e,
|
||||
if c.maxItems > 0 {
|
||||
if d <= 0 {
|
||||
// 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
|
||||
// whether the key was found.
|
||||
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
|
||||
item, found := c.items[k]
|
||||
if !found {
|
||||
c.mu.RUnlock()
|
||||
if c.maxItems > 0 {
|
||||
c.mu.Unlock()
|
||||
} else {
|
||||
c.mu.RUnlock()
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
if item.Expiration > 0 {
|
||||
if time.Now().UnixNano() > item.Expiration {
|
||||
c.mu.RUnlock()
|
||||
if c.maxItems > 0 {
|
||||
c.mu.Unlock()
|
||||
} else {
|
||||
c.mu.RUnlock()
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
if c.maxItems > 0 {
|
||||
c.mu.Unlock()
|
||||
} else {
|
||||
c.mu.RUnlock()
|
||||
}
|
||||
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) {
|
||||
item, found := c.items[k]
|
||||
if !found {
|
||||
@ -146,6 +208,10 @@ func (c *cache) get(k string) (interface{}, bool) {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
item.Accessed = time.Now().UnixNano()
|
||||
c.items[k] = item
|
||||
}
|
||||
return item.Object, true
|
||||
}
|
||||
|
||||
@ -161,6 +227,9 @@ func (c *cache) Increment(k string, n int64) error {
|
||||
c.mu.Unlock()
|
||||
return fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
switch v.Object.(type) {
|
||||
case int:
|
||||
v.Object = v.Object.(int) + int(n)
|
||||
@ -209,6 +278,9 @@ func (c *cache) IncrementFloat(k string, n float64) error {
|
||||
c.mu.Unlock()
|
||||
return fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
switch v.Object.(type) {
|
||||
case float32:
|
||||
v.Object = v.Object.(float32) + float32(n)
|
||||
@ -233,6 +305,9 @@ func (c *cache) IncrementInt(k string, n int) (int, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -255,6 +330,9 @@ func (c *cache) IncrementInt8(k string, n int8) (int8, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int8)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -277,6 +355,9 @@ func (c *cache) IncrementInt16(k string, n int16) (int16, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int16)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -299,6 +380,9 @@ func (c *cache) IncrementInt32(k string, n int32) (int32, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int32)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -321,6 +405,9 @@ func (c *cache) IncrementInt64(k string, n int64) (int64, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int64)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -343,6 +430,9 @@ func (c *cache) IncrementUint(k string, n uint) (uint, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -365,6 +455,9 @@ func (c *cache) IncrementUintptr(k string, n uintptr) (uintptr, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uintptr)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -387,6 +480,9 @@ func (c *cache) IncrementUint8(k string, n uint8) (uint8, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint8)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -409,6 +505,9 @@ func (c *cache) IncrementUint16(k string, n uint16) (uint16, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint16)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -431,6 +530,9 @@ func (c *cache) IncrementUint32(k string, n uint32) (uint32, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint32)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -453,6 +555,9 @@ func (c *cache) IncrementUint64(k string, n uint64) (uint64, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint64)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -475,6 +580,9 @@ func (c *cache) IncrementFloat32(k string, n float32) (float32, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(float32)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -497,6 +605,9 @@ func (c *cache) IncrementFloat64(k string, n float64) (float64, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(float64)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -523,6 +634,9 @@ func (c *cache) Decrement(k string, n int64) error {
|
||||
c.mu.Unlock()
|
||||
return fmt.Errorf("Item not found")
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
switch v.Object.(type) {
|
||||
case int:
|
||||
v.Object = v.Object.(int) - int(n)
|
||||
@ -571,6 +685,9 @@ func (c *cache) DecrementFloat(k string, n float64) error {
|
||||
c.mu.Unlock()
|
||||
return fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
switch v.Object.(type) {
|
||||
case float32:
|
||||
v.Object = v.Object.(float32) - float32(n)
|
||||
@ -595,6 +712,9 @@ func (c *cache) DecrementInt(k string, n int) (int, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -617,6 +737,9 @@ func (c *cache) DecrementInt8(k string, n int8) (int8, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int8)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -639,6 +762,9 @@ func (c *cache) DecrementInt16(k string, n int16) (int16, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int16)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -661,6 +787,9 @@ func (c *cache) DecrementInt32(k string, n int32) (int32, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int32)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -683,6 +812,9 @@ func (c *cache) DecrementInt64(k string, n int64) (int64, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(int64)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -705,6 +837,9 @@ func (c *cache) DecrementUint(k string, n uint) (uint, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -727,6 +862,9 @@ func (c *cache) DecrementUintptr(k string, n uintptr) (uintptr, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uintptr)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -749,6 +887,9 @@ func (c *cache) DecrementUint8(k string, n uint8) (uint8, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint8)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -771,6 +912,9 @@ func (c *cache) DecrementUint16(k string, n uint16) (uint16, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint16)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -793,6 +937,9 @@ func (c *cache) DecrementUint32(k string, n uint32) (uint32, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint32)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -815,6 +962,9 @@ func (c *cache) DecrementUint64(k string, n uint64) (uint64, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(uint64)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -837,6 +987,9 @@ func (c *cache) DecrementFloat32(k string, n float32) (float32, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(float32)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -859,6 +1012,9 @@ func (c *cache) DecrementFloat64(k string, n float64) (float64, error) {
|
||||
c.mu.Unlock()
|
||||
return 0, fmt.Errorf("Item %s not found", k)
|
||||
}
|
||||
if c.maxItems > 0 {
|
||||
v.Accessed = time.Now().UnixNano()
|
||||
}
|
||||
rv, ok := v.Object.(float64)
|
||||
if !ok {
|
||||
c.mu.Unlock()
|
||||
@ -875,9 +1031,10 @@ func (c *cache) DecrementFloat64(k string, n float64) (float64, error) {
|
||||
func (c *cache) Delete(k string) {
|
||||
c.mu.Lock()
|
||||
v, evicted := c.delete(k)
|
||||
evictFunc := c.onEvicted
|
||||
c.mu.Unlock()
|
||||
if evicted {
|
||||
c.onEvicted(k, v)
|
||||
evictFunc(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -902,8 +1059,9 @@ func (c *cache) DeleteExpired() {
|
||||
var evictedItems []keyAndValue
|
||||
now := time.Now().UnixNano()
|
||||
c.mu.Lock()
|
||||
evictFunc := c.onEvicted
|
||||
for k, v := range c.items {
|
||||
// "Inlining" of expired
|
||||
// "Inlining" of Expired
|
||||
if v.Expiration > 0 && now > v.Expiration {
|
||||
ov, evicted := c.delete(k)
|
||||
if evicted {
|
||||
@ -913,7 +1071,7 @@ func (c *cache) DeleteExpired() {
|
||||
}
|
||||
c.mu.Unlock()
|
||||
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()
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *cache) Flush() {
|
||||
c.mu.Lock()
|
||||
@ -1050,6 +1284,9 @@ func (j *janitor) Run(c *cache) {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
c.DeleteExpired()
|
||||
if c.maxItems > 0 {
|
||||
c.DeleteLRU()
|
||||
}
|
||||
case <-j.stop:
|
||||
ticker.Stop()
|
||||
return
|
||||
@ -1069,19 +1306,20 @@ func runJanitor(c *cache, ci time.Duration) {
|
||||
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 {
|
||||
de = -1
|
||||
}
|
||||
c := &cache{
|
||||
defaultExpiration: de,
|
||||
maxItems: mi,
|
||||
items: m,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
|
||||
c := newCache(de, m)
|
||||
func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item, mi int) *Cache {
|
||||
c := newCache(de, m, mi)
|
||||
// This trick ensures that the janitor goroutine (which--granted it
|
||||
// was enabled--is running DeleteExpired on c forever) does not keep
|
||||
// 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().
|
||||
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
|
||||
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
|
||||
@ -1127,5 +1380,10 @@ func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
|
||||
// map retrieved with c.Items(), and to register those same types before
|
||||
// decoding a blob containing an items map.
|
||||
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) {
|
||||
tc := New(DefaultExpiration, 0)
|
||||
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) {
|
||||
b.StopTimer()
|
||||
m := map[string]string{
|
||||
|
Loading…
Reference in New Issue
Block a user