229 lines
4.8 KiB
Go
229 lines
4.8 KiB
Go
package arc
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
list "github.com/bahlo/generic-list-go"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
//modify from https://github.com/alexanderGugel/arc
|
|
|
|
// Option is part of Functional Options Pattern
|
|
type Option[K comparable, V any] func(*ARC[K, V])
|
|
|
|
func WithSize[K comparable, V any](maxSize int) Option[K, V] {
|
|
return func(a *ARC[K, V]) {
|
|
a.c = maxSize
|
|
}
|
|
}
|
|
|
|
type ARC[K comparable, V any] struct {
|
|
p int
|
|
c int
|
|
t1 *list.List[*entry[K, V]]
|
|
b1 *list.List[*entry[K, V]]
|
|
t2 *list.List[*entry[K, V]]
|
|
b2 *list.List[*entry[K, V]]
|
|
mutex sync.Mutex
|
|
len int
|
|
cache map[K]*entry[K, V]
|
|
staleReturn bool
|
|
}
|
|
|
|
// New returns a new Adaptive Replacement Cache (ARC).
|
|
func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
|
|
arc := &ARC[K, V]{
|
|
p: 0,
|
|
t1: list.New[*entry[K, V]](),
|
|
b1: list.New[*entry[K, V]](),
|
|
t2: list.New[*entry[K, V]](),
|
|
b2: list.New[*entry[K, V]](),
|
|
len: 0,
|
|
cache: make(map[K]*entry[K, V]),
|
|
}
|
|
|
|
for _, option := range options {
|
|
option(arc)
|
|
}
|
|
return arc
|
|
}
|
|
|
|
// Set inserts a new key-value pair into the cache.
|
|
// This optimizes future access to this entry (side effect).
|
|
func (a *ARC[K, V]) Set(key K, value V) {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
|
|
a.set(key, value)
|
|
}
|
|
|
|
func (a *ARC[K, V]) set(key K, value V) {
|
|
a.setWithExpire(key, value, time.Unix(0, 0))
|
|
}
|
|
|
|
// SetWithExpire stores any representation of a response for a given key and given expires.
|
|
// The expires time will round to second.
|
|
func (a *ARC[K, V]) SetWithExpire(key K, value V, expires time.Time) {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
|
|
a.setWithExpire(key, value, expires)
|
|
}
|
|
|
|
func (a *ARC[K, V]) setWithExpire(key K, value V, expires time.Time) {
|
|
ent, ok := a.cache[key]
|
|
if ok != true {
|
|
a.len++
|
|
ent := &entry[K, V]{key: key, value: value, ghost: false, expires: expires.Unix()}
|
|
a.req(ent)
|
|
a.cache[key] = ent
|
|
} else {
|
|
if ent.ghost {
|
|
a.len++
|
|
}
|
|
ent.value = value
|
|
ent.ghost = false
|
|
ent.expires = expires.Unix()
|
|
a.req(ent)
|
|
}
|
|
}
|
|
|
|
// Get retrieves a previously via Set inserted entry.
|
|
// This optimizes future access to this entry (side effect).
|
|
func (a *ARC[K, V]) Get(key K) (value V, ok bool) {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
|
|
ent, ok := a.get(key)
|
|
if ok {
|
|
return ent.value, true
|
|
}
|
|
return lo.Empty[V](), false
|
|
}
|
|
|
|
func (a *ARC[K, V]) get(key K) (e *entry[K, V], ok bool) {
|
|
ent, ok := a.cache[key]
|
|
if ok {
|
|
a.req(ent)
|
|
return ent, !ent.ghost
|
|
}
|
|
return ent, false
|
|
}
|
|
|
|
// GetWithExpire returns any representation of a cached response,
|
|
// a time.Time Give expected expires,
|
|
// and a bool set to true if the key was found.
|
|
// This method will NOT check the maxAge of element and will NOT update the expires.
|
|
func (a *ARC[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
|
|
ent, ok := a.get(key)
|
|
if !ok {
|
|
return lo.Empty[V](), time.Time{}, false
|
|
}
|
|
|
|
return ent.value, time.Unix(ent.expires, 0), true
|
|
}
|
|
|
|
// Len determines the number of currently cached entries.
|
|
// This method is side-effect free in the sense that it does not attempt to optimize random cache access.
|
|
func (a *ARC[K, V]) Len() int {
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
|
|
return a.len
|
|
}
|
|
|
|
func (a *ARC[K, V]) req(ent *entry[K, V]) {
|
|
if ent.ll == a.t1 || ent.ll == a.t2 {
|
|
// Case I
|
|
ent.setMRU(a.t2)
|
|
} else if ent.ll == a.b1 {
|
|
// Case II
|
|
// Cache Miss in t1 and t2
|
|
|
|
// Adaptation
|
|
var d int
|
|
if a.b1.Len() >= a.b2.Len() {
|
|
d = 1
|
|
} else {
|
|
d = a.b2.Len() / a.b1.Len()
|
|
}
|
|
|
|
// a.p = min(a.p+d, a.c)
|
|
a.p = a.p + d
|
|
if a.c < a.p {
|
|
a.p = a.c
|
|
}
|
|
|
|
a.replace(ent)
|
|
ent.setMRU(a.t2)
|
|
} else if ent.ll == a.b2 {
|
|
// Case III
|
|
// Cache Miss in t1 and t2
|
|
|
|
// Adaptation
|
|
var d int
|
|
if a.b2.Len() >= a.b1.Len() {
|
|
d = 1
|
|
} else {
|
|
d = a.b1.Len() / a.b2.Len()
|
|
}
|
|
//a.p = max(a.p-d, 0)
|
|
a.p = a.p - d
|
|
if a.p < 0 {
|
|
a.p = 0
|
|
}
|
|
|
|
a.replace(ent)
|
|
ent.setMRU(a.t2)
|
|
} else if ent.ll == nil {
|
|
// Case IV
|
|
|
|
if a.t1.Len()+a.b1.Len() == a.c {
|
|
// Case A
|
|
if a.t1.Len() < a.c {
|
|
a.delLRU(a.b1)
|
|
a.replace(ent)
|
|
} else {
|
|
a.delLRU(a.t1)
|
|
}
|
|
} else if a.t1.Len()+a.b1.Len() < a.c {
|
|
// Case B
|
|
if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c {
|
|
if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c {
|
|
a.delLRU(a.b2)
|
|
}
|
|
a.replace(ent)
|
|
}
|
|
}
|
|
|
|
ent.setMRU(a.t1)
|
|
}
|
|
}
|
|
|
|
func (a *ARC[K, V]) delLRU(list *list.List[*entry[K, V]]) {
|
|
lru := list.Back()
|
|
list.Remove(lru)
|
|
a.len--
|
|
delete(a.cache, lru.Value.key)
|
|
}
|
|
|
|
func (a *ARC[K, V]) replace(ent *entry[K, V]) {
|
|
if a.t1.Len() > 0 && ((a.t1.Len() > a.p) || (ent.ll == a.b2 && a.t1.Len() == a.p)) {
|
|
lru := a.t1.Back().Value
|
|
lru.value = lo.Empty[V]()
|
|
lru.ghost = true
|
|
a.len--
|
|
lru.setMRU(a.b1)
|
|
} else {
|
|
lru := a.t2.Back().Value
|
|
lru.value = lo.Empty[V]()
|
|
lru.ghost = true
|
|
a.len--
|
|
lru.setMRU(a.b2)
|
|
}
|
|
}
|