mirror of
https://github.com/octoleo/syncthing.git
synced 2025-02-09 23:38:26 +00:00
157 lines
3.1 KiB
Go
157 lines
3.1 KiB
Go
|
package lfu
|
||
|
|
||
|
import (
|
||
|
"container/list"
|
||
|
"sync"
|
||
|
)
|
||
|
|
||
|
type Eviction struct {
|
||
|
Key string
|
||
|
Value interface{}
|
||
|
}
|
||
|
|
||
|
type Cache struct {
|
||
|
// If len > UpperBound, cache will automatically evict
|
||
|
// down to LowerBound. If either value is 0, this behavior
|
||
|
// is disabled.
|
||
|
UpperBound int
|
||
|
LowerBound int
|
||
|
values map[string]*cacheEntry
|
||
|
freqs *list.List
|
||
|
len int
|
||
|
lock *sync.Mutex
|
||
|
EvictionChannel chan<- Eviction
|
||
|
}
|
||
|
|
||
|
type cacheEntry struct {
|
||
|
key string
|
||
|
value interface{}
|
||
|
freqNode *list.Element
|
||
|
}
|
||
|
|
||
|
type listEntry struct {
|
||
|
entries map[*cacheEntry]byte
|
||
|
freq int
|
||
|
}
|
||
|
|
||
|
func New() *Cache {
|
||
|
c := new(Cache)
|
||
|
c.values = make(map[string]*cacheEntry)
|
||
|
c.freqs = list.New()
|
||
|
c.lock = new(sync.Mutex)
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *Cache) Get(key string) interface{} {
|
||
|
c.lock.Lock()
|
||
|
defer c.lock.Unlock()
|
||
|
if e, ok := c.values[key]; ok {
|
||
|
c.increment(e)
|
||
|
return e.value
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Cache) Set(key string, value interface{}) {
|
||
|
c.lock.Lock()
|
||
|
defer c.lock.Unlock()
|
||
|
if e, ok := c.values[key]; ok {
|
||
|
// value already exists for key. overwrite
|
||
|
e.value = value
|
||
|
c.increment(e)
|
||
|
} else {
|
||
|
// value doesn't exist. insert
|
||
|
e := new(cacheEntry)
|
||
|
e.key = key
|
||
|
e.value = value
|
||
|
c.values[key] = e
|
||
|
c.increment(e)
|
||
|
c.len++
|
||
|
// bounds mgmt
|
||
|
if c.UpperBound > 0 && c.LowerBound > 0 {
|
||
|
if c.len > c.UpperBound {
|
||
|
c.evict(c.len - c.LowerBound)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Cache) Len() int {
|
||
|
c.lock.Lock()
|
||
|
defer c.lock.Unlock()
|
||
|
return c.len
|
||
|
}
|
||
|
|
||
|
func (c *Cache) Evict(count int) int {
|
||
|
c.lock.Lock()
|
||
|
defer c.lock.Unlock()
|
||
|
return c.evict(count)
|
||
|
}
|
||
|
|
||
|
func (c *Cache) evict(count int) int {
|
||
|
// No lock here so it can be called
|
||
|
// from within the lock (during Set)
|
||
|
var evicted int
|
||
|
for i := 0; i < count; {
|
||
|
if place := c.freqs.Front(); place != nil {
|
||
|
for entry, _ := range place.Value.(*listEntry).entries {
|
||
|
if i < count {
|
||
|
if c.EvictionChannel != nil {
|
||
|
c.EvictionChannel <- Eviction{
|
||
|
Key: entry.key,
|
||
|
Value: entry.value,
|
||
|
}
|
||
|
}
|
||
|
delete(c.values, entry.key)
|
||
|
c.remEntry(place, entry)
|
||
|
evicted++
|
||
|
c.len--
|
||
|
i++
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return evicted
|
||
|
}
|
||
|
|
||
|
func (c *Cache) increment(e *cacheEntry) {
|
||
|
currentPlace := e.freqNode
|
||
|
var nextFreq int
|
||
|
var nextPlace *list.Element
|
||
|
if currentPlace == nil {
|
||
|
// new entry
|
||
|
nextFreq = 1
|
||
|
nextPlace = c.freqs.Front()
|
||
|
} else {
|
||
|
// move up
|
||
|
nextFreq = currentPlace.Value.(*listEntry).freq + 1
|
||
|
nextPlace = currentPlace.Next()
|
||
|
}
|
||
|
|
||
|
if nextPlace == nil || nextPlace.Value.(*listEntry).freq != nextFreq {
|
||
|
// create a new list entry
|
||
|
li := new(listEntry)
|
||
|
li.freq = nextFreq
|
||
|
li.entries = make(map[*cacheEntry]byte)
|
||
|
if currentPlace != nil {
|
||
|
nextPlace = c.freqs.InsertAfter(li, currentPlace)
|
||
|
} else {
|
||
|
nextPlace = c.freqs.PushFront(li)
|
||
|
}
|
||
|
}
|
||
|
e.freqNode = nextPlace
|
||
|
nextPlace.Value.(*listEntry).entries[e] = 1
|
||
|
if currentPlace != nil {
|
||
|
// remove from current position
|
||
|
c.remEntry(currentPlace, e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Cache) remEntry(place *list.Element, entry *cacheEntry) {
|
||
|
entries := place.Value.(*listEntry).entries
|
||
|
delete(entries, entry)
|
||
|
if len(entries) == 0 {
|
||
|
c.freqs.Remove(place)
|
||
|
}
|
||
|
}
|