forked from fossabot/clash
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve(fakeip): use lru cache to avoid outdate
- Loading branch information
Showing
9 changed files
with
386 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package cache | ||
|
||
// Modified by https://github.com/die-net/lrucache | ||
|
||
import ( | ||
"container/list" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// Option is part of Functional Options Pattern | ||
type Option func(*LruCache) | ||
|
||
// WithUpdateAgeOnGet update expires when Get element | ||
func WithUpdateAgeOnGet() Option { | ||
return func(l *LruCache) { | ||
l.updateAgeOnGet = true | ||
} | ||
} | ||
|
||
// WithAge defined element max age (second) | ||
func WithAge(maxAge int64) Option { | ||
return func(l *LruCache) { | ||
l.maxAge = maxAge | ||
} | ||
} | ||
|
||
// WithSize defined max length of LruCache | ||
func WithSize(maxSize int) Option { | ||
return func(l *LruCache) { | ||
l.maxSize = maxSize | ||
} | ||
} | ||
|
||
// LruCache is a thread-safe, in-memory lru-cache that evicts the | ||
// least recently used entries from memory when (if set) the entries are | ||
// older than maxAge (in seconds). Use the New constructor to create one. | ||
type LruCache struct { | ||
maxAge int64 | ||
maxSize int | ||
mu sync.Mutex | ||
cache map[interface{}]*list.Element | ||
lru *list.List // Front is least-recent | ||
updateAgeOnGet bool | ||
} | ||
|
||
// NewLRUCache creates an LruCache | ||
func NewLRUCache(options ...Option) *LruCache { | ||
lc := &LruCache{ | ||
lru: list.New(), | ||
cache: make(map[interface{}]*list.Element), | ||
} | ||
|
||
for _, option := range options { | ||
option(lc) | ||
} | ||
|
||
return lc | ||
} | ||
|
||
// Get returns the interface{} representation of a cached response and a bool | ||
// set to true if the key was found. | ||
func (c *LruCache) Get(key interface{}) (interface{}, bool) { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
|
||
le, ok := c.cache[key] | ||
if !ok { | ||
return nil, false | ||
} | ||
|
||
if c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() { | ||
c.deleteElement(le) | ||
c.maybeDeleteOldest() | ||
|
||
return nil, false | ||
} | ||
|
||
c.lru.MoveToBack(le) | ||
entry := le.Value.(*entry) | ||
if c.maxAge > 0 && c.updateAgeOnGet { | ||
entry.expires = time.Now().Unix() + c.maxAge | ||
} | ||
value := entry.value | ||
|
||
return value, true | ||
} | ||
|
||
// Set stores the interface{} representation of a response for a given key. | ||
func (c *LruCache) Set(key interface{}, value interface{}) { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
|
||
expires := int64(0) | ||
if c.maxAge > 0 { | ||
expires = time.Now().Unix() + c.maxAge | ||
} | ||
|
||
if le, ok := c.cache[key]; ok { | ||
c.lru.MoveToBack(le) | ||
e := le.Value.(*entry) | ||
e.value = value | ||
e.expires = expires | ||
} else { | ||
e := &entry{key: key, value: value, expires: expires} | ||
c.cache[key] = c.lru.PushBack(e) | ||
|
||
if c.maxSize > 0 { | ||
if len := c.lru.Len(); len > c.maxSize { | ||
c.deleteElement(c.lru.Front()) | ||
} | ||
} | ||
} | ||
|
||
c.maybeDeleteOldest() | ||
} | ||
|
||
// Delete removes the value associated with a key. | ||
func (c *LruCache) Delete(key string) { | ||
c.mu.Lock() | ||
|
||
if le, ok := c.cache[key]; ok { | ||
c.deleteElement(le) | ||
} | ||
|
||
c.mu.Unlock() | ||
} | ||
|
||
func (c *LruCache) maybeDeleteOldest() { | ||
if c.maxAge > 0 { | ||
now := time.Now().Unix() | ||
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() { | ||
c.deleteElement(le) | ||
} | ||
} | ||
} | ||
|
||
func (c *LruCache) deleteElement(le *list.Element) { | ||
c.lru.Remove(le) | ||
e := le.Value.(*entry) | ||
delete(c.cache, e.key) | ||
} | ||
|
||
type entry struct { | ||
key interface{} | ||
value interface{} | ||
expires int64 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package cache | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var entries = []struct { | ||
key string | ||
value string | ||
}{ | ||
{"1", "one"}, | ||
{"2", "two"}, | ||
{"3", "three"}, | ||
{"4", "four"}, | ||
{"5", "five"}, | ||
} | ||
|
||
func TestLRUCache(t *testing.T) { | ||
c := NewLRUCache() | ||
|
||
for _, e := range entries { | ||
c.Set(e.key, e.value) | ||
} | ||
|
||
c.Delete("missing") | ||
_, ok := c.Get("missing") | ||
assert.False(t, ok) | ||
|
||
for _, e := range entries { | ||
value, ok := c.Get(e.key) | ||
if assert.True(t, ok) { | ||
assert.Equal(t, e.value, value.(string)) | ||
} | ||
} | ||
|
||
for _, e := range entries { | ||
c.Delete(e.key) | ||
|
||
_, ok := c.Get(e.key) | ||
assert.False(t, ok) | ||
} | ||
} | ||
|
||
func TestLRUMaxAge(t *testing.T) { | ||
c := NewLRUCache(WithAge(86400)) | ||
|
||
now := time.Now().Unix() | ||
expected := now + 86400 | ||
|
||
// Add one expired entry | ||
c.Set("foo", "bar") | ||
c.lru.Back().Value.(*entry).expires = now | ||
|
||
// Reset | ||
c.Set("foo", "bar") | ||
e := c.lru.Back().Value.(*entry) | ||
assert.True(t, e.expires >= now) | ||
c.lru.Back().Value.(*entry).expires = now | ||
|
||
// Set a few and verify expiration times | ||
for _, s := range entries { | ||
c.Set(s.key, s.value) | ||
e := c.lru.Back().Value.(*entry) | ||
assert.True(t, e.expires >= expected && e.expires <= expected+10) | ||
} | ||
|
||
// Make sure we can get them all | ||
for _, s := range entries { | ||
_, ok := c.Get(s.key) | ||
assert.True(t, ok) | ||
} | ||
|
||
// Expire all entries | ||
for _, s := range entries { | ||
le, ok := c.cache[s.key] | ||
if assert.True(t, ok) { | ||
le.Value.(*entry).expires = now | ||
} | ||
} | ||
|
||
// Get one expired entry, which should clear all expired entries | ||
_, ok := c.Get("3") | ||
assert.False(t, ok) | ||
assert.Equal(t, c.lru.Len(), 0) | ||
} | ||
|
||
func TestLRUpdateOnGet(t *testing.T) { | ||
c := NewLRUCache(WithAge(86400), WithUpdateAgeOnGet()) | ||
|
||
now := time.Now().Unix() | ||
expires := now + 86400/2 | ||
|
||
// Add one expired entry | ||
c.Set("foo", "bar") | ||
c.lru.Back().Value.(*entry).expires = expires | ||
|
||
_, ok := c.Get("foo") | ||
assert.True(t, ok) | ||
assert.True(t, c.lru.Back().Value.(*entry).expires > expires) | ||
} | ||
|
||
func TestMaxSize(t *testing.T) { | ||
c := NewLRUCache(WithSize(2)) | ||
// Add one expired entry | ||
c.Set("foo", "bar") | ||
_, ok := c.Get("foo") | ||
assert.True(t, ok) | ||
|
||
c.Set("bar", "foo") | ||
c.Set("baz", "foo") | ||
|
||
_, ok = c.Get("foo") | ||
assert.False(t, ok) | ||
} |
Oops, something went wrong.