-
Notifications
You must be signed in to change notification settings - Fork 1.1k
cubemaster: fix the data races in localcache #1376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d9e7a85
7215762
0765499
389d18b
9f269a2
9c1ecd5
90ad2c5
35cba98
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,6 +65,8 @@ type LocalCache struct { | |
| localCacheConfig *LocalCacheConfig | ||
| sharedCalls util.SharedCalls | ||
| consecutiveFailNum int64 | ||
| expiredUse atomic.Bool | ||
| destroyOnce sync.Once | ||
| } | ||
|
|
||
| func NewCache(name string, loader LoaderFunc, localCacheConfig *LocalCacheConfig) *LocalCache { | ||
|
|
@@ -77,6 +79,7 @@ func NewCache(name string, loader LoaderFunc, localCacheConfig *LocalCacheConfig | |
| localCache.loadFile(localCacheConfig.LoadFileName) | ||
| } | ||
| localCache.localCacheConfig = localCache.SetupConfig(localCacheConfig) | ||
| localCache.expiredUse.Store(localCache.localCacheConfig != nil && localCache.localCacheConfig.ExpiredUse) | ||
|
|
||
| localCache.chShrinkCache = make(chan bool, 1) | ||
| localCache.chCacheExit = make(chan bool) | ||
|
|
@@ -99,29 +102,35 @@ func NewCache(name string, loader LoaderFunc, localCacheConfig *LocalCacheConfig | |
| } | ||
|
|
||
| func (localCache *LocalCache) Destroy() { | ||
| if localCache != nil { | ||
| if localCache == nil { | ||
| return | ||
| } | ||
| if localCache.chCacheExit == nil { | ||
| return | ||
| } | ||
| localCache.destroyOnce.Do(func() { | ||
| CubeLog.Infof("LruCache(%s) Destroy", localCache.name) | ||
| if localCache.chCacheExit != nil { | ||
| if localCache.localCacheConfig.OpenCacheFile { | ||
| localCache.saveFile(localCache.localCacheConfig.LoadFileName) | ||
| } | ||
| localCache.cache.Flush() | ||
| close(localCache.chCacheExit) | ||
| localCache.waitGroup.Wait() | ||
| localCache.chCacheExit = nil | ||
| if localCache.localCacheConfig.OpenCacheFile { | ||
| localCache.saveFile(localCache.localCacheConfig.LoadFileName) | ||
| } | ||
| } | ||
| localCache.cache.Flush() | ||
| close(localCache.chCacheExit) | ||
| localCache.waitGroup.Wait() | ||
| }) | ||
| } | ||
|
|
||
| func (localCache *LocalCache) Get(ctx context.Context, key string) (interface{}, bool, error) { | ||
| item, found := localCache.cache.Get(key) | ||
| if found { | ||
| element := item.(*list.Element) | ||
|
|
||
| localCache.Lock() | ||
| itm := element.Value.(*util.CacheValue) | ||
| localCache.Unlock() | ||
|
|
||
| if time.Now().Add(-itm.Expired).After(time.Unix(itm.LastAccess, 0)) { | ||
|
|
||
| if !localCache.localCacheConfig.ExpiredUse { | ||
| if !localCache.expiredUse.Load() { | ||
| r, f, err := localCache.loadAndRefresh(ctx, key) | ||
|
|
||
| if err != nil && localCache.localCacheConfig.DemotionExpiredUse { | ||
|
|
@@ -159,14 +168,17 @@ func (localCache *LocalCache) put(key string, val interface{}, expired time.Dura | |
| if item, found := localCache.cache.Get(key); found { | ||
| element := item.(*list.Element) | ||
| localCache.Lock() | ||
| prev := element.Value.(*util.CacheValue) | ||
| next := &util.CacheValue{ | ||
| Key: prev.Key, | ||
| Value: val, | ||
| LastAccess: time.Now().Unix(), | ||
| Expired: expired} | ||
| element.Value = next | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is a newly-introduced data race. Before this PR, Fix suggestion: snapshot the |
||
| localCache.valueList.MoveToBack(element) | ||
| localCache.Unlock() | ||
| itm := element.Value.(*util.CacheValue) | ||
| atomic.AddInt64(&localCache.curCacheSize, -itm.Size()) | ||
| itm.Value = val | ||
| itm.Expired = expired | ||
| itm.LastAccess = time.Now().Unix() | ||
| atomic.AddInt64(&localCache.curCacheSize, itm.Size()) | ||
| atomic.AddInt64(&localCache.curCacheSize, -prev.Size()) | ||
| atomic.AddInt64(&localCache.curCacheSize, next.Size()) | ||
| } else { | ||
| itm := &util.CacheValue{ | ||
| Key: key, | ||
|
|
@@ -180,8 +192,11 @@ func (localCache *LocalCache) put(key string, val interface{}, expired time.Dura | |
| localCache.cache.Set(key, element, -1) | ||
| } | ||
|
|
||
| if localCache.curCacheSize >= localCache.localCacheConfig.HighCacheSize { | ||
| localCache.chShrinkCache <- true | ||
| if atomic.LoadInt64(&localCache.curCacheSize) >= localCache.localCacheConfig.HighCacheSize { | ||
| select { | ||
| case localCache.chShrinkCache <- true: | ||
| default: | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -198,7 +213,7 @@ func (localCache *LocalCache) loadAndRefresh(ctx context.Context, key string) (i | |
| CubeLog.Errorf("Cache LoadAndRefresh Error:%s, %v, %s", key, found, err) | ||
| return nil, err | ||
| } else { | ||
| localCache.consecutiveFailNum = 0 | ||
| atomic.StoreInt64(&localCache.consecutiveFailNum, 0) | ||
| } | ||
|
|
||
| if !found { | ||
|
|
@@ -240,10 +255,10 @@ func (localCache *LocalCache) shrinkCache() { | |
| select { | ||
| case <-localCache.chShrinkCache: | ||
| curTime := time.Now() | ||
| curCacheSize := localCache.curCacheSize | ||
| curCacheSize := atomic.LoadInt64(&localCache.curCacheSize) | ||
| var shrinkNum, shrinkSize, size int64 | ||
| for { | ||
| if localCache.curCacheSize > localCache.localCacheConfig.LowCacheSize { | ||
| if atomic.LoadInt64(&localCache.curCacheSize) > localCache.localCacheConfig.LowCacheSize { | ||
| localCache.Lock() | ||
| element := localCache.valueList.Front() | ||
| if element == nil { | ||
|
|
@@ -305,12 +320,12 @@ func (localCache *LocalCache) errStrategy() { | |
| int64(localCache.localCacheConfig.MaxConsecutiveFailNum) { | ||
|
|
||
| if localCache.localCacheConfig.DemotionExpiredUse { | ||
| localCache.localCacheConfig.ExpiredUse = true | ||
| localCache.expiredUse.Store(true) | ||
| } | ||
| } else { | ||
|
|
||
| if localCache.localCacheConfig.DemotionExpiredUse && localCache.localCacheConfig.ExpiredUse { | ||
| localCache.localCacheConfig.ExpiredUse = false | ||
| if localCache.localCacheConfig.DemotionExpiredUse && localCache.expiredUse.Load() { | ||
| localCache.expiredUse.Store(false) | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -324,10 +339,13 @@ func (localCache *LocalCache) saveFile(file string) { | |
| var itm *util.CacheValue | ||
| switch value := item.Object.(type) { | ||
| case *list.Element: | ||
| localCache.Lock() | ||
| stored := value.Value | ||
| localCache.Unlock() | ||
| var ok bool | ||
| itm, ok = value.Value.(*util.CacheValue) | ||
| itm, ok = stored.(*util.CacheValue) | ||
| if !ok { | ||
| CubeLog.Errorf("Cache(%s) cannot persist key %s: list element contains %T", localCache.name, key, value.Value) | ||
| CubeLog.Errorf("Cache(%s) cannot persist key %s: list element contains %T", localCache.name, key, stored) | ||
| continue | ||
| } | ||
| case *util.CacheValue: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description says
Getreads the*CacheValuepointer and does itsMoveToBackinside one critical section, but theMoveToBackat the end ofGetis still a separate lock acquisition. The separation is actually load-bearing — promoting the entry before the refresh decision would make a failed refresh promote the entry, whichTestFailingRefreshDoesNotPromoteTheEntryexplicitly forbids — so the code is correct, just worth a comment. Note it also means every hit now acquires the cache-wide mutex twice (snapshot read + MoveToBack), doubling exclusive-lock contention on the hot read path of this shared cache.