mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-03 20:07:38 +03:00
291 lines
5.9 KiB
Go
291 lines
5.9 KiB
Go
package cache
|
|
|
|
// Modified by https://github.com/die-net/lrucache
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing/common"
|
|
"github.com/sagernet/sing/common/x/list"
|
|
)
|
|
|
|
type Option[K comparable, V any] func(*LruCache[K, V])
|
|
|
|
type EvictCallback[K comparable, V any] func(key K, value V)
|
|
|
|
func WithEvict[K comparable, V any](cb EvictCallback[K, V]) Option[K, V] {
|
|
return func(l *LruCache[K, V]) {
|
|
l.onEvict = cb
|
|
}
|
|
}
|
|
|
|
func WithUpdateAgeOnGet[K comparable, V any]() Option[K, V] {
|
|
return func(l *LruCache[K, V]) {
|
|
l.updateAgeOnGet = true
|
|
}
|
|
}
|
|
|
|
func WithAge[K comparable, V any](maxAge int64) Option[K, V] {
|
|
return func(l *LruCache[K, V]) {
|
|
l.maxAge = maxAge
|
|
}
|
|
}
|
|
|
|
func WithSize[K comparable, V any](maxSize int) Option[K, V] {
|
|
return func(l *LruCache[K, V]) {
|
|
l.maxSize = maxSize
|
|
}
|
|
}
|
|
|
|
func WithStale[K comparable, V any](stale bool) Option[K, V] {
|
|
return func(l *LruCache[K, V]) {
|
|
l.staleReturn = stale
|
|
}
|
|
}
|
|
|
|
type LruCache[K comparable, V any] struct {
|
|
maxAge int64
|
|
maxSize int
|
|
mu sync.Mutex
|
|
cache map[K]*list.Element[*entry[K, V]]
|
|
lru list.List[*entry[K, V]] // Front is least-recent
|
|
updateAgeOnGet bool
|
|
staleReturn bool
|
|
onEvict EvictCallback[K, V]
|
|
}
|
|
|
|
func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
|
lc := &LruCache[K, V]{
|
|
cache: make(map[K]*list.Element[*entry[K, V]]),
|
|
}
|
|
|
|
for _, option := range options {
|
|
option(lc)
|
|
}
|
|
|
|
return lc
|
|
}
|
|
|
|
func (c *LruCache[K, V]) Load(key K) (V, bool) {
|
|
entry := c.get(key)
|
|
if entry == nil {
|
|
return common.DefaultValue[V](), false
|
|
}
|
|
value := entry.value
|
|
|
|
return value, true
|
|
}
|
|
|
|
func (c *LruCache[K, V]) LoadOrStore(key K, constructor func() V) (V, bool) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
le, ok := c.cache[key]
|
|
if ok {
|
|
if c.maxAge > 0 && le.Value.expires <= time.Now().Unix() {
|
|
c.deleteElement(le)
|
|
goto create
|
|
}
|
|
|
|
c.lru.MoveToBack(le)
|
|
entry := le.Value
|
|
if c.maxAge > 0 && c.updateAgeOnGet {
|
|
entry.expires = time.Now().Unix() + c.maxAge
|
|
}
|
|
return entry.value, true
|
|
}
|
|
|
|
create:
|
|
value := constructor()
|
|
if le, ok := c.cache[key]; ok {
|
|
c.lru.MoveToBack(le)
|
|
e := le.Value
|
|
e.value = value
|
|
e.expires = time.Now().Unix() + c.maxAge
|
|
} else {
|
|
e := &entry[K, V]{key: key, value: value, expires: time.Now().Unix() + c.maxAge}
|
|
c.cache[key] = c.lru.PushBack(e)
|
|
}
|
|
|
|
c.maybeDeleteOldest()
|
|
return value, false
|
|
}
|
|
|
|
func (c *LruCache[K, V]) LoadOrStoreWithAge(key K, maxAge int64, constructor func() V) (V, bool) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if maxAge == 0 {
|
|
maxAge = c.maxAge
|
|
}
|
|
|
|
le, ok := c.cache[key]
|
|
if ok {
|
|
if c.maxAge > 0 && le.Value.expires <= time.Now().Unix() {
|
|
c.deleteElement(le)
|
|
goto create
|
|
}
|
|
|
|
c.lru.MoveToBack(le)
|
|
entry := le.Value
|
|
if c.maxAge > 0 && c.updateAgeOnGet {
|
|
entry.expires = time.Now().Unix() + maxAge
|
|
}
|
|
return entry.value, true
|
|
}
|
|
|
|
create:
|
|
value := constructor()
|
|
if le, ok := c.cache[key]; ok {
|
|
c.lru.MoveToBack(le)
|
|
e := le.Value
|
|
e.value = value
|
|
e.expires = time.Now().Unix() + maxAge
|
|
} else {
|
|
e := &entry[K, V]{key: key, value: value, expires: time.Now().Unix() + c.maxAge}
|
|
c.cache[key] = c.lru.PushBack(e)
|
|
}
|
|
|
|
c.maybeDeleteOldest()
|
|
return value, false
|
|
}
|
|
|
|
func (c *LruCache[K, V]) LoadWithExpire(key K) (V, time.Time, bool) {
|
|
entry := c.get(key)
|
|
if entry == nil {
|
|
return common.DefaultValue[V](), time.Time{}, false
|
|
}
|
|
|
|
return entry.value, time.Unix(entry.expires, 0), true
|
|
}
|
|
|
|
func (c *LruCache[K, V]) Exist(key K) bool {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
_, ok := c.cache[key]
|
|
return ok
|
|
}
|
|
|
|
func (c *LruCache[K, V]) Store(key K, value V) {
|
|
expires := int64(0)
|
|
if c.maxAge > 0 {
|
|
expires = time.Now().Unix() + c.maxAge
|
|
}
|
|
c.StoreWithExpire(key, value, time.Unix(expires, 0))
|
|
}
|
|
|
|
func (c *LruCache[K, V]) StoreWithExpire(key K, value V, expires time.Time) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if le, ok := c.cache[key]; ok {
|
|
c.lru.MoveToBack(le)
|
|
e := le.Value
|
|
e.value = value
|
|
e.expires = expires.Unix()
|
|
} else {
|
|
e := &entry[K, V]{key: key, value: value, expires: expires.Unix()}
|
|
c.cache[key] = c.lru.PushBack(e)
|
|
|
|
if c.maxSize > 0 {
|
|
if n := c.lru.Len(); n > c.maxSize {
|
|
c.deleteElement(c.lru.Front())
|
|
}
|
|
}
|
|
}
|
|
|
|
c.maybeDeleteOldest()
|
|
}
|
|
|
|
func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
|
|
n.lru = list.List[*entry[K, V]]{}
|
|
n.cache = make(map[K]*list.Element[*entry[K, V]])
|
|
|
|
for e := c.lru.Front(); e != nil; e = e.Next() {
|
|
elm := e.Value
|
|
n.cache[elm.key] = n.lru.PushBack(elm)
|
|
}
|
|
}
|
|
|
|
func (c *LruCache[K, V]) Range(block func(key K, value V)) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
for le := c.lru.Front(); le != nil; le = le.Next() {
|
|
block(le.Value.key, le.Value.value)
|
|
}
|
|
}
|
|
|
|
func (c *LruCache[K, V]) get(key K) *entry[K, V] {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
le, ok := c.cache[key]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if !c.staleReturn && c.maxAge > 0 && le.Value.expires <= time.Now().Unix() {
|
|
c.deleteElement(le)
|
|
c.maybeDeleteOldest()
|
|
|
|
return nil
|
|
}
|
|
|
|
c.lru.MoveToBack(le)
|
|
entry := le.Value
|
|
if c.maxAge > 0 && c.updateAgeOnGet {
|
|
entry.expires = time.Now().Unix() + c.maxAge
|
|
}
|
|
return entry
|
|
}
|
|
|
|
// Delete removes the value associated with a key.
|
|
func (c *LruCache[K, V]) Delete(key K) {
|
|
c.mu.Lock()
|
|
|
|
if le, ok := c.cache[key]; ok {
|
|
c.deleteElement(le)
|
|
}
|
|
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
func (c *LruCache[K, V]) Clear() {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
for element := c.lru.Front(); element != nil; element = element.Next() {
|
|
c.deleteElement(element)
|
|
}
|
|
}
|
|
|
|
func (c *LruCache[K, V]) maybeDeleteOldest() {
|
|
if !c.staleReturn && c.maxAge > 0 {
|
|
now := time.Now().Unix()
|
|
for le := c.lru.Front(); le != nil && le.Value.expires <= now; le = c.lru.Front() {
|
|
c.deleteElement(le)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
|
|
c.lru.Remove(le)
|
|
e := le.Value
|
|
delete(c.cache, e.key)
|
|
if c.onEvict != nil {
|
|
c.onEvict(e.key, e.value)
|
|
}
|
|
}
|
|
|
|
type entry[K comparable, V any] struct {
|
|
key K
|
|
value V
|
|
expires int64
|
|
}
|