diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c4becd8..f78d1292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [Fix 4435: Controller SaveToFile remove all temp file](https://github.com/beego/beego/pull/5138) - [Fix 5079: Split signalChan into flushChan and closeChan](https://github.com/beego/beego/pull/5139) - [Fix 5172: protect field access with lock to avoid possible data race](https://github.com/beego/beego/pull/5210) +- [cache: fix typo and optimize the naming]() # v2.0.7 - [Upgrade github.com/go-kit/kit, CVE-2022-24450](https://github.com/beego/beego/pull/5121) diff --git a/client/cache/bloom_filter_cache.go b/client/cache/bloom_filter_cache.go index 6dceb3af..385ae63c 100644 --- a/client/cache/bloom_filter_cache.go +++ b/client/cache/bloom_filter_cache.go @@ -24,7 +24,7 @@ import ( type BloomFilterCache struct { Cache - BloomFilter + blm BloomFilter loadFunc func(ctx context.Context, key string) (any, error) expiration time.Duration // set cache expiration, default never expire } @@ -34,7 +34,7 @@ type BloomFilter interface { Add(data string) } -func NewBloomFilterCache(cache Cache, ln func(context.Context, string) (any, error), blm BloomFilter, +func NewBloomFilterCache(cache Cache, ln func(ctx context.Context, key string) (any, error), blm BloomFilter, expiration time.Duration, ) (*BloomFilterCache, error) { if cache == nil || ln == nil || blm == nil { @@ -42,10 +42,10 @@ func NewBloomFilterCache(cache Cache, ln func(context.Context, string) (any, err } return &BloomFilterCache{ - Cache: cache, - BloomFilter: blm, - loadFunc: ln, - expiration: expiration, + Cache: cache, + blm: blm, + loadFunc: ln, + expiration: expiration, }, nil } @@ -55,7 +55,7 @@ func (bfc *BloomFilterCache) Get(ctx context.Context, key string) (any, error) { return nil, err } if errors.Is(err, ErrKeyNotExist) { - exist := bfc.BloomFilter.Test(key) + exist := bfc.blm.Test(key) if exist { val, err = bfc.loadFunc(ctx, key) if err != nil { diff --git a/client/cache/bloom_filter_cache_test.go b/client/cache/bloom_filter_cache_test.go index f160abbb..ad15f2b4 100644 --- a/client/cache/bloom_filter_cache_test.go +++ b/client/cache/bloom_filter_cache_test.go @@ -18,6 +18,7 @@ package cache import ( "context" "errors" + "fmt" "sync" "testing" "time" @@ -175,29 +176,31 @@ func TestBloomFilterCache_Get(t *testing.T) { } } -// This implementation of Bloom filters cache is NOT safe for concurrent use. -// Uncomment the following method. -// func TestBloomFilterCache_Get_Concurrency(t *testing.T) { -// bfc, err := NewBloomFilterCache(cacheUnderlying, loadFunc, mockBloom, time.Minute) -// assert.Nil(t, err) -// -// _ = mockDB.Db.ClearAll(context.Background()) -// _ = mockDB.Db.Put(context.Background(), "key_11", "value_11", 0) -// mockBloom.AddString("key_11") -// -// var wg sync.WaitGroup -// wg.Add(100000) -// for i := 0; i < 100000; i++ { -// key := fmt.Sprintf("key_%d", i) -// go func(key string) { -// defer wg.Done() -// val, _ := bfc.Get(context.Background(), key) -// -// if val != nil { -// assert.Equal(t, "value_11", val) -// } -// }(key) -// } -// wg.Wait() -// assert.Equal(t, int64(1), mockDB.loadCnt) -// } +func ExampleNewBloomFilterCache() { + c := NewMemoryCache() + c, err := NewBloomFilterCache(c, func(ctx context.Context, key string) (any, error) { + return fmt.Sprintf("hello, %s", key), nil + }, &AlwaysExist{}, time.Minute) + if err != nil { + panic(err) + } + + val, err := c.Get(context.Background(), "Beego") + if err != nil { + panic(err) + } + fmt.Println(val) + // Output: + // hello, Beego +} + +type AlwaysExist struct { +} + +func (*AlwaysExist) Test(string) bool { + return true +} + +func (*AlwaysExist) Add(string) { + +} diff --git a/client/cache/random_expired_cache.go b/client/cache/random_expired_cache.go index 26200112..395a0a92 100644 --- a/client/cache/random_expired_cache.go +++ b/client/cache/random_expired_cache.go @@ -24,8 +24,8 @@ import ( // RandomExpireCacheOption implement genreate random time offset expired option type RandomExpireCacheOption func(*RandomExpireCache) -// WithOffsetFunc returns a RandomExpireCacheOption that configures the offset function -func WithOffsetFunc(fn func() time.Duration) RandomExpireCacheOption { +// WithRandomExpireOffsetFunc returns a RandomExpireCacheOption that configures the offset function +func WithRandomExpireOffsetFunc(fn func() time.Duration) RandomExpireCacheOption { return func(cache *RandomExpireCache) { cache.offset = fn } diff --git a/client/cache/random_expired_cache_test.go b/client/cache/random_expired_cache_test.go index 38a475d6..07cdb7a1 100644 --- a/client/cache/random_expired_cache_test.go +++ b/client/cache/random_expired_cache_test.go @@ -16,6 +16,7 @@ package cache import ( "context" + "fmt" "math/rand" "strings" "testing" @@ -86,14 +87,42 @@ func TestRandomExpireCache(t *testing.T) { assert.True(t, strings.Contains(err.Error(), "key isn't exist")) } -func TestWithOffsetFunc(t *testing.T) { +func TestWithRandomExpireOffsetFunc(t *testing.T) { bm, err := NewCache("memory", `{"interval":20}`) assert.Nil(t, err) magic := -time.Duration(rand.Int()) - cache := NewRandomExpireCache(bm, WithOffsetFunc(func() time.Duration { + cache := NewRandomExpireCache(bm, WithRandomExpireOffsetFunc(func() time.Duration { return magic })) // offset should return the magic value assert.Equal(t, magic, cache.(*RandomExpireCache).offset()) } + +func ExampleNewRandomExpireCache() { + mc := NewMemoryCache() + // use the default strategy which will generate random time offset (range: [3s,8s)) expired + c := NewRandomExpireCache(mc) + // so the expiration will be [1m3s, 1m8s) + err := c.Put(context.Background(), "hello", "world", time.Minute) + if err != nil { + panic(err) + } + + c = NewRandomExpireCache(mc, + // based on the expiration + WithRandomExpireOffsetFunc(func() time.Duration { + val := rand.Int31n(100) + fmt.Printf("calculate offset") + return time.Duration(val) * time.Second + })) + + // so the expiration will be [1m0s, 1m100s) + err = c.Put(context.Background(), "hello", "world", time.Minute) + if err != nil { + panic(err) + } + + // Output: + // calculate offset +} diff --git a/client/cache/read_through_test.go b/client/cache/read_through_test.go index c73b6b6d..45f12f84 100644 --- a/client/cache/read_through_test.go +++ b/client/cache/read_through_test.go @@ -17,6 +17,7 @@ package cache import ( "context" "errors" + "fmt" "testing" "time" @@ -143,3 +144,28 @@ func (m *MockOrm) Load(key string) (any, error) { } return m.kvs[key], nil } + +func ExampleNewReadThroughCache() { + c := NewMemoryCache() + var err error + c, err = NewReadThroughCache(c, + // expiration, same as the expiration of key + time.Minute, + // load func, how to load data if the key is absent. + // in general, you should load data from database. + func(ctx context.Context, key string) (any, error) { + return fmt.Sprintf("hello, %s", key), nil + }) + if err != nil { + panic(err) + } + + val, err := c.Get(context.Background(), "Beego") + if err != nil { + panic(err) + } + fmt.Print(val) + + // Output: + // hello, Beego +} diff --git a/client/cache/singleflight_test.go b/client/cache/singleflight_test.go index af691767..268bb8f9 100644 --- a/client/cache/singleflight_test.go +++ b/client/cache/singleflight_test.go @@ -16,6 +16,7 @@ package cache import ( "context" + "fmt" "sync" "testing" "time" @@ -70,3 +71,20 @@ func testSingleflightCacheConcurrencyGet(t *testing.T, bm Cache) { } wg.Wait() } + +func ExampleNewSingleflightCache() { + c := NewMemoryCache() + c, err := NewSingleflightCache(c, time.Minute, func(ctx context.Context, key string) (any, error) { + return fmt.Sprintf("hello, %s", key), nil + }) + if err != nil { + panic(err) + } + val, err := c.Get(context.Background(), "Beego") + if err != nil { + panic(err) + } + fmt.Print(val) + // Output: + // hello, Beego +} diff --git a/client/cache/write_through.go b/client/cache/write_through.go index 10334b2e..6bf957b8 100644 --- a/client/cache/write_through.go +++ b/client/cache/write_through.go @@ -22,24 +22,26 @@ import ( "github.com/beego/beego/v2/core/berror" ) -type WriteThoughCache struct { +type WriteThroughCache struct { Cache storeFunc func(ctx context.Context, key string, val any) error } -func NewWriteThoughCache(cache Cache, fn func(ctx context.Context, key string, val any) error) (*WriteThoughCache, error) { +// NewWriteThroughCache creates a write through cache pattern decorator. +// The fn is the function that persistent the key and val. +func NewWriteThroughCache(cache Cache, fn func(ctx context.Context, key string, val any) error) (*WriteThroughCache, error) { if fn == nil || cache == nil { return nil, berror.Error(InvalidInitParameters, "cache or storeFunc can not be nil") } - w := &WriteThoughCache{ + w := &WriteThroughCache{ Cache: cache, storeFunc: fn, } return w, nil } -func (w *WriteThoughCache) Set(ctx context.Context, key string, val any, expiration time.Duration) error { +func (w *WriteThroughCache) Set(ctx context.Context, key string, val any, expiration time.Duration) error { err := w.storeFunc(ctx, key, val) if err != nil { return berror.Wrap(err, PersistCacheFailed, fmt.Sprintf("key: %s, val: %v", key, val)) diff --git a/client/cache/write_through_test.go b/client/cache/write_through_test.go index a483b79c..d76af643 100644 --- a/client/cache/write_through_test.go +++ b/client/cache/write_through_test.go @@ -60,7 +60,7 @@ func TestWriteThoughCache_Set(t *testing.T) { } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - w, err := NewWriteThoughCache(tt.cache, tt.storeFunc) + w, err := NewWriteThroughCache(tt.cache, tt.storeFunc) if err != nil { assert.EqualError(t, tt.wantErr, err.Error()) return @@ -94,7 +94,7 @@ func TestNewWriteThoughCache(t *testing.T) { tests := []struct { name string args args - wantRes *WriteThoughCache + wantRes *WriteThroughCache wantErr error }{ { @@ -119,7 +119,7 @@ func TestNewWriteThoughCache(t *testing.T) { cache: underlyingCache, fn: storeFunc, }, - wantRes: &WriteThoughCache{ + wantRes: &WriteThroughCache{ Cache: underlyingCache, storeFunc: storeFunc, }, @@ -127,7 +127,7 @@ func TestNewWriteThoughCache(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := NewWriteThoughCache(tt.args.cache, tt.args.fn) + _, err := NewWriteThroughCache(tt.args.cache, tt.args.fn) assert.Equal(t, tt.wantErr, err) if err != nil { return @@ -135,3 +135,21 @@ func TestNewWriteThoughCache(t *testing.T) { }) } } + +func ExampleNewWriteThroughCache() { + c := NewMemoryCache() + wtc, err := NewWriteThroughCache(c, func(ctx context.Context, key string, val any) error { + fmt.Printf("write data to somewhere key %s, val %v \n", key, val) + return nil + }) + if err != nil { + panic(err) + } + err = wtc.Set(context.Background(), + "/biz/user/id=1", "I am user 1", time.Minute) + if err != nil { + panic(err) + } + // Output: + // write data to somewhere key /biz/user/id=1, val I am user 1 +}