Adding upstream version 2.2.2.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
a6909e3829
commit
02ef45af86
19 changed files with 3024 additions and 0 deletions
146
janitor.go
Normal file
146
janitor.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
package gocache
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// JanitorShiftTarget is the target number of expired keys to find during passive clean up duty
|
||||
// before pausing the passive expired keys eviction process
|
||||
JanitorShiftTarget = 25
|
||||
|
||||
// JanitorMaxIterationsPerShift is the maximum number of nodes to traverse before pausing
|
||||
//
|
||||
// This is to prevent the janitor from traversing the entire cache, which could take a long time
|
||||
// to complete depending on the size of the cache.
|
||||
//
|
||||
// By limiting it to a small number, we are effectively reducing the impact of passive eviction.
|
||||
JanitorMaxIterationsPerShift = 1000
|
||||
|
||||
// JanitorMinShiftBackOff is the minimum interval between each iteration of steps
|
||||
// defined by JanitorMaxIterationsPerShift
|
||||
JanitorMinShiftBackOff = 50 * time.Millisecond
|
||||
|
||||
// JanitorMaxShiftBackOff is the maximum interval between each iteration of steps
|
||||
// defined by JanitorMaxIterationsPerShift
|
||||
JanitorMaxShiftBackOff = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
// StartJanitor starts the janitor on a different goroutine
|
||||
// The janitor's job is to delete expired keys in the background, in other words, it takes care of passive eviction.
|
||||
// It can be stopped by calling Cache.StopJanitor.
|
||||
// If you do not start the janitor, expired keys will only be deleted when they are accessed through Get, GetByKeys, or
|
||||
// GetAll.
|
||||
func (cache *Cache) StartJanitor() error {
|
||||
if cache.stopJanitor != nil {
|
||||
return ErrJanitorAlreadyRunning
|
||||
}
|
||||
cache.stopJanitor = make(chan bool)
|
||||
go func() {
|
||||
// rather than starting from the tail on every run, we can try to start from the last traversed entry
|
||||
var lastTraversedNode *Entry
|
||||
totalNumberOfExpiredKeysInPreviousRunFromTailToHead := 0
|
||||
backOff := JanitorMinShiftBackOff
|
||||
for {
|
||||
select {
|
||||
case <-time.After(backOff):
|
||||
// Passive clean up duty
|
||||
cache.mutex.Lock()
|
||||
if cache.tail != nil {
|
||||
start := time.Now()
|
||||
steps := 0
|
||||
expiredEntriesFound := 0
|
||||
current := cache.tail
|
||||
if lastTraversedNode != nil {
|
||||
// Make sure the lastTraversedNode is still in the cache, otherwise we might be traversing nodes that were already deleted.
|
||||
// Furthermore, we need to make sure that the entry from the cache has the same pointer as the lastTraversedNode
|
||||
// to verify that there isn't just a new cache entry with the same key (i.e. in case lastTraversedNode got evicted)
|
||||
if entryFromCache, isInCache := cache.get(lastTraversedNode.Key); isInCache && entryFromCache == lastTraversedNode {
|
||||
current = lastTraversedNode
|
||||
}
|
||||
}
|
||||
if current == cache.tail {
|
||||
if Debug {
|
||||
log.Printf("There are currently %d entries in the cache. The last walk resulted in finding %d expired keys", len(cache.entries), totalNumberOfExpiredKeysInPreviousRunFromTailToHead)
|
||||
}
|
||||
totalNumberOfExpiredKeysInPreviousRunFromTailToHead = 0
|
||||
}
|
||||
for current != nil {
|
||||
// since we're walking from the tail to the head, we get the previous reference
|
||||
var previous *Entry
|
||||
steps++
|
||||
if current.Expired() {
|
||||
expiredEntriesFound++
|
||||
// Because delete will remove the previous reference from the entry, we need to store the
|
||||
// previous reference before we delete it
|
||||
previous = current.previous
|
||||
cache.delete(current.Key)
|
||||
cache.stats.ExpiredKeys++
|
||||
}
|
||||
if current == cache.head {
|
||||
lastTraversedNode = nil
|
||||
break
|
||||
}
|
||||
// Travel to the current node's previous node only if no specific previous node has been specified
|
||||
if previous != nil {
|
||||
current = previous
|
||||
} else {
|
||||
current = current.previous
|
||||
}
|
||||
lastTraversedNode = current
|
||||
if steps == JanitorMaxIterationsPerShift || expiredEntriesFound >= JanitorShiftTarget {
|
||||
if expiredEntriesFound > 0 {
|
||||
backOff = JanitorMinShiftBackOff
|
||||
} else {
|
||||
if backOff*2 <= JanitorMaxShiftBackOff {
|
||||
backOff *= 2
|
||||
} else {
|
||||
backOff = JanitorMaxShiftBackOff
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if Debug {
|
||||
log.Printf("traversed %d nodes and found %d expired entries in %s before stopping\n", steps, expiredEntriesFound, time.Since(start))
|
||||
}
|
||||
totalNumberOfExpiredKeysInPreviousRunFromTailToHead += expiredEntriesFound
|
||||
} else {
|
||||
if backOff*2 < JanitorMaxShiftBackOff {
|
||||
backOff *= 2
|
||||
} else {
|
||||
backOff = JanitorMaxShiftBackOff
|
||||
}
|
||||
}
|
||||
cache.mutex.Unlock()
|
||||
case <-cache.stopJanitor:
|
||||
cache.stopJanitor <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
//if Debug {
|
||||
// go func() {
|
||||
// var m runtime.MemStats
|
||||
// for {
|
||||
// runtime.ReadMemStats(&m)
|
||||
// log.Printf("Alloc=%vMB; HeapReleased=%vMB; Sys=%vMB; HeapInUse=%vMB; HeapObjects=%v; HeapObjectsFreed=%v; GC=%v; cache.memoryUsage=%vMB; cacheSize=%d\n", m.Alloc/1024/1024, m.HeapReleased/1024/1024, m.Sys/1024/1024, m.HeapInuse/1024/1024, m.HeapObjects, m.Frees, m.NumGC, cache.memoryUsage/1024/1024, cache.Count())
|
||||
// time.Sleep(3 * time.Second)
|
||||
// }
|
||||
// }()
|
||||
//}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopJanitor stops the janitor
|
||||
func (cache *Cache) StopJanitor() {
|
||||
if cache.stopJanitor != nil {
|
||||
// Tell the janitor to stop, and then wait for the janitor to reply on the same channel that it's stopping
|
||||
// This may seem a bit odd, but this allows us to avoid a data race condition when trying to set
|
||||
// cache.stopJanitor to nil
|
||||
cache.stopJanitor <- true
|
||||
<-cache.stopJanitor
|
||||
cache.stopJanitor = nil
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue