diff --git a/CHANGELOG.md b/CHANGELOG.md index f45ff708..04941303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # developing +- [Fix 4984: random expire cache](https://github.com/beego/beego/pull/4984) - [Fix issue 4961, `leafInfo.match()` use `path.join()` to deal with `wildcardValues`, which may lead to cross directory risk ](https://github.com/beego/beego/pull/4964) - [Fix 4975: graceful server listen the specific address](https://github.com/beego/beego/pull/4979) - [Fix 4976: make admin serve HTTP only](https://github.com/beego/beego/pull/4980) + + # v2.0.3 - [upgrade redisgo to v1.8.8](https://github.com/beego/beego/pull/4872) - [fix prometheus CVE-2022-21698](https://github.com/beego/beego/pull/4878) diff --git a/client/cache/random_expired_cache.go b/client/cache/random_expired_cache.go new file mode 100644 index 00000000..eeebf880 --- /dev/null +++ b/client/cache/random_expired_cache.go @@ -0,0 +1,52 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cache + +import ( + "context" + "math/rand" + "time" +) + +// RandomExpireCacheOption implement genreate random time offset expired option +type RandomExpireCacheOption func(*RandomExpireCache) + +// RandomExpireCache prevent cache batch invalidation +// Cache random time offset expired +type RandomExpireCache struct { + Cache + offset func() time.Duration +} + +// Put random time offset expired +func (rec *RandomExpireCache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { + timeout += rec.offset() + return rec.Cache.Put(ctx, key, val, timeout) +} + +// NewRandomExpireCache return random expire cache struct +func NewRandomExpireCache(adapter Cache, opts ...RandomExpireCacheOption) Cache { + var rec RandomExpireCache + rec.Cache = adapter + for _, fn := range opts { + fn(&rec) + } + return &rec +} + +// defaultExpiredFunc genreate random time offset expired +func defaultExpiredFunc() time.Duration { + return time.Duration(rand.Intn(5)+3) * time.Second +} diff --git a/client/cache/random_expired_cache_test.go b/client/cache/random_expired_cache_test.go new file mode 100644 index 00000000..b1af5840 --- /dev/null +++ b/client/cache/random_expired_cache_test.go @@ -0,0 +1,88 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cache + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestRandomExpireCache(t *testing.T) { + bm, err := NewCache("memory", `{"interval":20}`) + assert.Nil(t, err) + + // cache := NewRandomExpireCache(bm) + cache := NewRandomExpireCache(bm, func(opt *RandomExpireCache) { + opt.offset = defaultExpiredFunc + }) + + timeoutDuration := 3 * time.Second + + if err = cache.Put(context.Background(), "Leon Ding", 22, timeoutDuration); err != nil { + t.Error("set Error", err) + } + + // testing random expire cache + time.Sleep(timeoutDuration + 3 + time.Second) + + if res, _ := cache.IsExist(context.Background(), "Leon Ding"); !res { + t.Error("check err") + } + + if v, _ := cache.Get(context.Background(), "Leon Ding"); v.(int) != 22 { + t.Error("get err") + } + + cache.Delete(context.Background(), "Leon Ding") + res, _ := cache.IsExist(context.Background(), "Leon Ding") + assert.False(t, res) + + assert.Nil(t, cache.Put(context.Background(), "Leon Ding", "author", timeoutDuration)) + + cache.Delete(context.Background(), "astaxie") + res, _ = cache.IsExist(context.Background(), "astaxie") + assert.False(t, res) + + assert.Nil(t, cache.Put(context.Background(), "astaxie", "author", timeoutDuration)) + + res, _ = cache.IsExist(context.Background(), "astaxie") + assert.True(t, res) + + v, _ := cache.Get(context.Background(), "astaxie") + assert.Equal(t, "author", v) + + assert.Nil(t, cache.Put(context.Background(), "astaxie1", "author1", timeoutDuration)) + + res, _ = cache.IsExist(context.Background(), "astaxie1") + assert.True(t, res) + + vv, _ := cache.GetMulti(context.Background(), []string{"astaxie", "astaxie1"}) + assert.Equal(t, 2, len(vv)) + assert.Equal(t, "author", vv[0]) + assert.Equal(t, "author1", vv[1]) + + vv, err = cache.GetMulti(context.Background(), []string{"astaxie0", "astaxie1"}) + assert.Equal(t, 2, len(vv)) + assert.Nil(t, vv[0]) + assert.Equal(t, "author1", vv[1]) + + assert.NotNil(t, err) + assert.True(t, strings.Contains(err.Error(), "key isn't exist")) + +}