diff --git a/cache/cache.go b/cache/cache.go index 55ff972..04989b1 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -22,12 +22,19 @@ func New[K comparable, V any](opts ...Option[K, V]) Cache[K, V] { } for _, opt := range opts { - opt(obj) + go opt(obj) } return obj } +func (v *_cache[K, V]) Size() int { + v.mux.RLock() + defer v.mux.RUnlock() + + return len(v.list) +} + func (v *_cache[K, V]) Has(key K) bool { v.mux.RLock() defer v.mux.RUnlock() diff --git a/cache/cache_test.go b/cache/cache_test.go index dd10291..7419b80 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -62,12 +62,12 @@ type testValue struct { func (v testValue) Timestamp() int64 { return v.TS } -func TestUnit_AutoClean(t *testing.T) { +func TestUnit_OptTimeClean(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := cache.New[string, testValue]( - cache.AutoClean[string, testValue](ctx, time.Millisecond*100), + cache.OptTimeClean[string, testValue](ctx, time.Millisecond*100), ) c.Set("foo", testValue{Val: "bar", TS: time.Now().Add(time.Millisecond * 200).Unix()}) @@ -78,12 +78,30 @@ func TestUnit_AutoClean(t *testing.T) { casecheck.False(t, c.Has("foo")) } +func TestUnit_OptCountRandomClean(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c := cache.New[int, int]( + cache.OptCountRandomClean[int, int](ctx, 100, time.Second), + ) + + for i := 0; i < 200; i++ { + c.Set(i, i) + } + casecheck.Equal(t, 200, c.Size()) + + time.Sleep(time.Second * 2) + + casecheck.Equal(t, 100, c.Size()) +} + func Benchmark_New(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := cache.New[string, testValue]( - cache.AutoClean[string, testValue](ctx, time.Millisecond*100), + cache.OptTimeClean[string, testValue](ctx, time.Millisecond*100), ) b.ReportAllocs() diff --git a/cache/options.go b/cache/options.go index 04e1b88..66d2c8d 100644 --- a/cache/options.go +++ b/cache/options.go @@ -12,30 +12,79 @@ import ( "go.osspkg.com/routine" ) -func AutoClean[K comparable, V Timestamp](ctx context.Context, interval time.Duration) Option[K, V] { +func OptTimeClean[K comparable, V Timestamp](ctx context.Context, interval time.Duration) Option[K, V] { return func(v *_cache[K, V]) { - routine.Interval(ctx, interval, func(ctx context.Context) { - curr := time.Now().Unix() - keys := make([]K, 0, 10) - - v.mux.RLock() - for key, value := range v.list { - if value.Timestamp() < curr { - keys = append(keys, key) - } - } - v.mux.RUnlock() - - if len(keys) == 0 { - return - } - - v.mux.Lock() - defer v.mux.Unlock() - - for _, key := range keys { - delete(v.list, key) - } - }) + + tik := routine.Ticker{ + Interval: interval, + OnStart: false, + Calls: []routine.TickFunc{ + func(ctx context.Context, t time.Time) { + curr := t.Unix() + keys := make([]K, 0, 10) + + v.mux.RLock() + for key, value := range v.list { + if value.Timestamp() < curr { + keys = append(keys, key) + } + } + v.mux.RUnlock() + + if len(keys) == 0 { + return + } + + v.mux.Lock() + defer v.mux.Unlock() + + for _, key := range keys { + delete(v.list, key) + } + }, + }, + } + + tik.Run(ctx) + } +} + +func OptCountRandomClean[K comparable, V any](ctx context.Context, maxCount int, interval time.Duration) Option[K, V] { + return func(v *_cache[K, V]) { + + if maxCount < 0 { + panic("OptCountRandomClean: maxCount < 0") + } + + tik := routine.Ticker{ + Interval: interval, + OnStart: false, + Calls: []routine.TickFunc{ + func(ctx context.Context, _ time.Time) { + + v.mux.RLock() + removeCount := len(v.list) - maxCount + v.mux.RUnlock() + + if removeCount <= 0 { + return + } + + v.mux.Lock() + defer v.mux.Unlock() + + for key := range v.list { + if removeCount <= 0 { + return + } + + delete(v.list, key) + removeCount-- + } + }, + }, + } + + tik.Run(ctx) } } diff --git a/cache/types.go b/cache/types.go index 31ee40c..3d3895a 100644 --- a/cache/types.go +++ b/cache/types.go @@ -13,6 +13,7 @@ type Cache[K comparable, V any] interface { Replace(data map[K]V) Del(key K) Keys() []K + Size() int Flush() } diff --git a/go.mod b/go.mod index 6c59a3b..3d1ffc2 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v1.5.0 go.osspkg.com/casecheck v0.3.0 go.osspkg.com/errors v0.3.1 - go.osspkg.com/routine v0.3.1 - go.osspkg.com/syncing v0.3.1 + go.osspkg.com/routine v0.4.0 + go.osspkg.com/syncing v0.4.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 89bdca5..5e15ffa 100644 --- a/go.sum +++ b/go.sum @@ -4,10 +4,10 @@ go.osspkg.com/casecheck v0.3.0 h1:x15blEszElbrHrEH5H02JIIhGIg/lGZzIt1kQlD3pwM= go.osspkg.com/casecheck v0.3.0/go.mod h1:TRFXDMFJEOtnlp3ET2Hix3osbxwPWhvaiT/HfD3+gBA= go.osspkg.com/errors v0.3.1 h1:F9m/EEd/Ot2jba/TV7tvVRIpWXzIpNLc7vRJKcBD86A= go.osspkg.com/errors v0.3.1/go.mod h1:dKXe6Rt07nzY7OyKQNZ8HGBicZ2uQ5TKEoVFnVFOK44= -go.osspkg.com/routine v0.3.1 h1:R0o4P0Ml5eoeHc2DiHjRvHBo/XXrW5nJNqIj3ToRzjg= -go.osspkg.com/routine v0.3.1/go.mod h1:z5AvvTbB19/tt1E5JOb4POhK1tOPgmejajgao/IWn4s= -go.osspkg.com/syncing v0.3.1 h1:zt5o/X5DQ/GE5OQTKkq1nNWJMg7EcYhw0YiwMGuA0f8= -go.osspkg.com/syncing v0.3.1/go.mod h1:Dpe0ljlEG6cI2Y9PxEjKiYEX2sgs1eUjWNVjFu4/iB0= +go.osspkg.com/routine v0.4.0 h1:fEDOI3BTaM/rt5pYT+qPq4gFxvw8WJVY2jMeczB7F9A= +go.osspkg.com/routine v0.4.0/go.mod h1:HUVnPHLFzNCYEGOqUiQWL+av73aFJ6H57XutFxhA+eU= +go.osspkg.com/syncing v0.4.0 h1:9ytMfGHd6Ew69D2n/1syj0FTujNfb1KBiUSZ+KsMTqk= +go.osspkg.com/syncing v0.4.0/go.mod h1:/LBmgCAHFW6nQgVDILpEuo6eRCFK1yyFeNbDs4eVNls= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=