Merge branch 'dev' of https://github.com/Dreamacro/clash into Alpha
This commit is contained in:
106
common/cache/cache.go
vendored
106
common/cache/cache.go
vendored
@ -1,106 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cache store element with a expired time
|
||||
type Cache[K comparable, V any] struct {
|
||||
*cache[K, V]
|
||||
}
|
||||
|
||||
type cache[K comparable, V any] struct {
|
||||
mapping sync.Map
|
||||
janitor *janitor[K, V]
|
||||
}
|
||||
|
||||
type element[V any] struct {
|
||||
Expired time.Time
|
||||
Payload V
|
||||
}
|
||||
|
||||
// Put element in Cache with its ttl
|
||||
func (c *cache[K, V]) Put(key K, payload V, ttl time.Duration) {
|
||||
c.mapping.Store(key, &element[V]{
|
||||
Payload: payload,
|
||||
Expired: time.Now().Add(ttl),
|
||||
})
|
||||
}
|
||||
|
||||
// Get element in Cache, and drop when it expired
|
||||
func (c *cache[K, V]) Get(key K) V {
|
||||
item, exist := c.mapping.Load(key)
|
||||
if !exist {
|
||||
return getZero[V]()
|
||||
}
|
||||
elm := item.(*element[V])
|
||||
// expired
|
||||
if time.Since(elm.Expired) > 0 {
|
||||
c.mapping.Delete(key)
|
||||
return getZero[V]()
|
||||
}
|
||||
return elm.Payload
|
||||
}
|
||||
|
||||
// GetWithExpire element in Cache with Expire Time
|
||||
func (c *cache[K, V]) GetWithExpire(key K) (payload V, expired time.Time) {
|
||||
item, exist := c.mapping.Load(key)
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
elm := item.(*element[V])
|
||||
// expired
|
||||
if time.Since(elm.Expired) > 0 {
|
||||
c.mapping.Delete(key)
|
||||
return
|
||||
}
|
||||
return elm.Payload, elm.Expired
|
||||
}
|
||||
|
||||
func (c *cache[K, V]) cleanup() {
|
||||
c.mapping.Range(func(k, v any) bool {
|
||||
key := k.(string)
|
||||
elm := v.(*element[V])
|
||||
if time.Since(elm.Expired) > 0 {
|
||||
c.mapping.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
type janitor[K comparable, V any] struct {
|
||||
interval time.Duration
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func (j *janitor[K, V]) process(c *cache[K, V]) {
|
||||
ticker := time.NewTicker(j.interval)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
c.cleanup()
|
||||
case <-j.stop:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stopJanitor[K comparable, V any](c *Cache[K, V]) {
|
||||
c.janitor.stop <- struct{}{}
|
||||
}
|
||||
|
||||
// New return *Cache
|
||||
func New[K comparable, V any](interval time.Duration) *Cache[K, V] {
|
||||
j := &janitor[K, V]{
|
||||
interval: interval,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
c := &cache[K, V]{janitor: j}
|
||||
go j.process(c)
|
||||
C := &Cache[K, V]{c}
|
||||
runtime.SetFinalizer(C, stopJanitor[K, V])
|
||||
return C
|
||||
}
|
72
common/cache/cache_test.go
vendored
72
common/cache/cache_test.go
vendored
@ -1,72 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCache_Basic(t *testing.T) {
|
||||
interval := 200 * time.Millisecond
|
||||
ttl := 20 * time.Millisecond
|
||||
c := New[string, int](interval)
|
||||
c.Put("int", 1, ttl)
|
||||
|
||||
d := New[string, string](interval)
|
||||
d.Put("string", "a", ttl)
|
||||
|
||||
i := c.Get("int")
|
||||
assert.Equal(t, i, 1, "should recv 1")
|
||||
|
||||
s := d.Get("string")
|
||||
assert.Equal(t, s, "a", "should recv 'a'")
|
||||
}
|
||||
|
||||
func TestCache_TTL(t *testing.T) {
|
||||
interval := 200 * time.Millisecond
|
||||
ttl := 20 * time.Millisecond
|
||||
now := time.Now()
|
||||
c := New[string, int](interval)
|
||||
c.Put("int", 1, ttl)
|
||||
c.Put("int2", 2, ttl)
|
||||
|
||||
i := c.Get("int")
|
||||
_, expired := c.GetWithExpire("int2")
|
||||
assert.Equal(t, i, 1, "should recv 1")
|
||||
assert.True(t, now.Before(expired))
|
||||
|
||||
time.Sleep(ttl * 2)
|
||||
i = c.Get("int")
|
||||
j, _ := c.GetWithExpire("int2")
|
||||
assert.True(t, i == 0, "should recv 0")
|
||||
assert.True(t, j == 0, "should recv 0")
|
||||
}
|
||||
|
||||
func TestCache_AutoCleanup(t *testing.T) {
|
||||
interval := 10 * time.Millisecond
|
||||
ttl := 15 * time.Millisecond
|
||||
c := New[string, int](interval)
|
||||
c.Put("int", 1, ttl)
|
||||
|
||||
time.Sleep(ttl * 2)
|
||||
i := c.Get("int")
|
||||
j, _ := c.GetWithExpire("int")
|
||||
assert.True(t, i == 0, "should recv 0")
|
||||
assert.True(t, j == 0, "should recv 0")
|
||||
}
|
||||
|
||||
func TestCache_AutoGC(t *testing.T) {
|
||||
sign := make(chan struct{})
|
||||
go func() {
|
||||
interval := 10 * time.Millisecond
|
||||
ttl := 15 * time.Millisecond
|
||||
c := New[string, int](interval)
|
||||
c.Put("int", 1, ttl)
|
||||
sign <- struct{}{}
|
||||
}()
|
||||
|
||||
<-sign
|
||||
runtime.GC()
|
||||
}
|
4
common/cache/lrucache.go
vendored
4
common/cache/lrucache.go
vendored
@ -65,8 +65,8 @@ type LruCache[K comparable, V any] struct {
|
||||
onEvict EvictCallback[K, V]
|
||||
}
|
||||
|
||||
// NewLRUCache creates an LruCache
|
||||
func NewLRUCache[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
||||
// New creates an LruCache
|
||||
func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
||||
lc := &LruCache[K, V]{
|
||||
lru: list.New[*entry[K, V]](),
|
||||
cache: make(map[K]*list.Element[*entry[K, V]]),
|
||||
|
20
common/cache/lrucache_test.go
vendored
20
common/cache/lrucache_test.go
vendored
@ -19,7 +19,7 @@ var entries = []struct {
|
||||
}
|
||||
|
||||
func TestLRUCache(t *testing.T) {
|
||||
c := NewLRUCache[string, string]()
|
||||
c := New[string, string]()
|
||||
|
||||
for _, e := range entries {
|
||||
c.Set(e.key, e.value)
|
||||
@ -45,7 +45,7 @@ func TestLRUCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLRUMaxAge(t *testing.T) {
|
||||
c := NewLRUCache[string, string](WithAge[string, string](86400))
|
||||
c := New[string, string](WithAge[string, string](86400))
|
||||
|
||||
now := time.Now().Unix()
|
||||
expected := now + 86400
|
||||
@ -88,7 +88,7 @@ func TestLRUMaxAge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLRUpdateOnGet(t *testing.T) {
|
||||
c := NewLRUCache[string, string](WithAge[string, string](86400), WithUpdateAgeOnGet[string, string]())
|
||||
c := New[string, string](WithAge[string, string](86400), WithUpdateAgeOnGet[string, string]())
|
||||
|
||||
now := time.Now().Unix()
|
||||
expires := now + 86400/2
|
||||
@ -103,7 +103,7 @@ func TestLRUpdateOnGet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMaxSize(t *testing.T) {
|
||||
c := NewLRUCache[string, string](WithSize[string, string](2))
|
||||
c := New[string, string](WithSize[string, string](2))
|
||||
// Add one expired entry
|
||||
c.Set("foo", "bar")
|
||||
_, ok := c.Get("foo")
|
||||
@ -117,7 +117,7 @@ func TestMaxSize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExist(t *testing.T) {
|
||||
c := NewLRUCache[int, int](WithSize[int, int](1))
|
||||
c := New[int, int](WithSize[int, int](1))
|
||||
c.Set(1, 2)
|
||||
assert.True(t, c.Exist(1))
|
||||
c.Set(2, 3)
|
||||
@ -130,7 +130,7 @@ func TestEvict(t *testing.T) {
|
||||
temp = key + value
|
||||
}
|
||||
|
||||
c := NewLRUCache[int, int](WithEvict[int, int](evict), WithSize[int, int](1))
|
||||
c := New[int, int](WithEvict[int, int](evict), WithSize[int, int](1))
|
||||
c.Set(1, 2)
|
||||
c.Set(2, 3)
|
||||
|
||||
@ -138,7 +138,7 @@ func TestEvict(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetWithExpire(t *testing.T) {
|
||||
c := NewLRUCache[int, *struct{}](WithAge[int, *struct{}](1))
|
||||
c := New[int, *struct{}](WithAge[int, *struct{}](1))
|
||||
now := time.Now().Unix()
|
||||
|
||||
tenSecBefore := time.Unix(now-10, 0)
|
||||
@ -153,7 +153,7 @@ func TestSetWithExpire(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStale(t *testing.T) {
|
||||
c := NewLRUCache[int, int](WithAge[int, int](1), WithStale[int, int](true))
|
||||
c := New[int, int](WithAge[int, int](1), WithStale[int, int](true))
|
||||
now := time.Now().Unix()
|
||||
|
||||
tenSecBefore := time.Unix(now-10, 0)
|
||||
@ -166,11 +166,11 @@ func TestStale(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCloneTo(t *testing.T) {
|
||||
o := NewLRUCache[string, int](WithSize[string, int](10))
|
||||
o := New[string, int](WithSize[string, int](10))
|
||||
o.Set("1", 1)
|
||||
o.Set("2", 2)
|
||||
|
||||
n := NewLRUCache[string, int](WithSize[string, int](2))
|
||||
n := New[string, int](WithSize[string, int](2))
|
||||
n.Set("3", 3)
|
||||
n.Set("4", 4)
|
||||
|
||||
|
@ -137,3 +137,45 @@ func TestStructure_Nest(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s.BazOptional, goal)
|
||||
}
|
||||
|
||||
func TestStructure_SliceNilValue(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"foo": 1,
|
||||
"bar": []any{"bar", nil},
|
||||
}
|
||||
|
||||
goal := &BazSlice{
|
||||
Foo: 1,
|
||||
Bar: []string{"bar", ""},
|
||||
}
|
||||
|
||||
s := &BazSlice{}
|
||||
err := weakTypeDecoder.Decode(rawMap, s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, goal.Bar, s.Bar)
|
||||
|
||||
s = &BazSlice{}
|
||||
err = decoder.Decode(rawMap, s)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestStructure_SliceNilValueComplex(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"bar": []any{map[string]any{"bar": "foo"}, nil},
|
||||
}
|
||||
|
||||
s := &struct {
|
||||
Bar []map[string]any `test:"bar"`
|
||||
}{}
|
||||
|
||||
err := decoder.Decode(rawMap, s)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, s.Bar[1])
|
||||
|
||||
ss := &struct {
|
||||
Bar []Baz `test:"bar"`
|
||||
}{}
|
||||
|
||||
err = decoder.Decode(rawMap, ss)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user