Skip to content

Commit b25bd3d

Browse files
authored
Add protection on namespace cache in case namespace is not created (#824)
1 parent 9d5c94e commit b25bd3d

File tree

2 files changed

+71
-22
lines changed

2 files changed

+71
-22
lines changed

common/cache/namespaceCache.go

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
package cache
2828

2929
import (
30+
"fmt"
3031
"hash/fnv"
3132
"sort"
3233
"strconv"
@@ -65,6 +66,8 @@ const (
6566
namespaceCacheInitialSize = 10 * 1024
6667
namespaceCacheMaxSize = 64 * 1024
6768
namespaceCacheTTL = 0 // 0 means infinity
69+
// NamespaceCacheMinRefreshInterval is a minimun namespace cache refresh interval.
70+
NamespaceCacheMinRefreshInterval = 2 * time.Second
6871
// NamespaceCacheRefreshInterval namespace cache refresh interval
6972
NamespaceCacheRefreshInterval = 10 * time.Second
7073
// NamespaceCacheRefreshFailureRetryInterval is the wait time
@@ -116,7 +119,10 @@ type (
116119

117120
// refresh lock is used to guarantee at most one
118121
// coroutine is doing namespace refreshment
119-
refreshLock sync.Mutex
122+
refreshLock sync.Mutex
123+
lastRefreshTime atomic.Value
124+
checkLock sync.Mutex
125+
lastCheckTime time.Time
120126

121127
callbackLock sync.Mutex
122128
prepareCallbacks map[int32]PrepareCallbackFn
@@ -165,6 +171,7 @@ func NewNamespaceCache(
165171
}
166172
cache.cacheNameToID.Store(newNamespaceCache())
167173
cache.cacheByID.Store(newNamespaceCache())
174+
cache.lastRefreshTime.Store(time.Time{})
168175

169176
return cache
170177
}
@@ -316,8 +323,8 @@ func (c *namespaceCache) RegisterNamespaceChangeCallback(
316323
// with namespace change version.
317324
sort.Sort(namespaces)
318325

319-
prevEntries := []*NamespaceCacheEntry{}
320-
nextEntries := []*NamespaceCacheEntry{}
326+
var prevEntries []*NamespaceCacheEntry
327+
var nextEntries []*NamespaceCacheEntry
321328
for _, namespace := range namespaces {
322329
if namespace.notificationVersion >= initialNotificationVersion {
323330
prevEntries = append(prevEntries, nil)
@@ -420,6 +427,8 @@ func (c *namespaceCache) refreshNamespaces() error {
420427
// this function only refresh the namespaces in the v2 table
421428
// the namespaces in the v1 table will be refreshed if cache is stale
422429
func (c *namespaceCache) refreshNamespacesLocked() error {
430+
now := c.timeSource.Now()
431+
423432
// first load the metadata record, then load namespaces
424433
// this can guarantee that namespaces in the cache are not updated more than metadata record
425434
metadata, err := c.metadataMgr.GetMetadata()
@@ -451,8 +460,8 @@ func (c *namespaceCache) refreshNamespacesLocked() error {
451460
// with namespace change version.
452461
sort.Sort(namespaces)
453462

454-
prevEntries := []*NamespaceCacheEntry{}
455-
nextEntries := []*NamespaceCacheEntry{}
463+
var prevEntries []*NamespaceCacheEntry
464+
var nextEntries []*NamespaceCacheEntry
456465

457466
// make a copy of the existing namespace cache, so we can calculate diff and do compare and swap
458467
newCacheNameToID := newNamespaceCache()
@@ -492,16 +501,35 @@ UpdateLoop:
492501
c.cacheByID.Store(newCacheByID)
493502
c.cacheNameToID.Store(newCacheNameToID)
494503
c.triggerNamespaceChangeCallbackLocked(prevEntries, nextEntries)
504+
505+
// only update last refresh time when refresh succeeded
506+
c.lastRefreshTime.Store(now)
495507
return nil
496508
}
497509

498-
func (c *namespaceCache) checkNamespaceExists(
510+
func (c *namespaceCache) checkAndContinue(
499511
name string,
500512
id string,
501-
) error {
513+
) (bool, error) {
514+
now := c.timeSource.Now()
515+
if now.Sub(c.lastRefreshTime.Load().(time.Time)) < NamespaceCacheMinRefreshInterval {
516+
return false, nil
517+
}
518+
519+
c.checkLock.Lock()
520+
defer c.checkLock.Unlock()
502521

522+
now = c.timeSource.Now()
523+
if now.Sub(c.lastCheckTime) < NamespaceCacheMinRefreshInterval {
524+
return true, nil
525+
}
526+
527+
c.lastCheckTime = now
503528
_, err := c.metadataMgr.GetNamespace(&persistence.GetNamespaceRequest{Name: name, ID: id})
504-
return err
529+
if err != nil {
530+
return false, err
531+
}
532+
return true, nil
505533
}
506534

507535
func (c *namespaceCache) updateNameToIDCache(
@@ -563,9 +591,13 @@ func (c *namespaceCache) getNamespace(
563591
return c.getNamespaceByID(id, true)
564592
}
565593

566-
if err := c.checkNamespaceExists(name, ""); err != nil {
594+
doContinue, err := c.checkAndContinue(name, "")
595+
if err != nil {
567596
return nil, err
568597
}
598+
if !doContinue {
599+
return nil, serviceerror.NewNotFound(fmt.Sprintf("namespace: %v not found", name))
600+
}
569601

570602
c.refreshLock.Lock()
571603
defer c.refreshLock.Unlock()
@@ -581,7 +613,7 @@ func (c *namespaceCache) getNamespace(
581613
return c.getNamespaceByID(id, true)
582614
}
583615
// impossible case
584-
return nil, serviceerror.NewInternal("namespaceCache encounter case where namespace exists but cannot be loaded")
616+
return nil, serviceerror.NewNotFound(fmt.Sprintf("namespace: %v not found", name))
585617
}
586618

587619
// getNamespaceByID retrieves the information from the cache if it exists, otherwise retrieves the information from metadata
@@ -603,9 +635,13 @@ func (c *namespaceCache) getNamespaceByID(
603635
return result, nil
604636
}
605637

606-
if err := c.checkNamespaceExists("", id); err != nil {
638+
doContinue, err := c.checkAndContinue("", id)
639+
if err != nil {
607640
return nil, err
608641
}
642+
if !doContinue {
643+
return nil, serviceerror.NewNotFound(fmt.Sprintf("namespace ID: %v not found", id))
644+
}
609645

610646
c.refreshLock.Lock()
611647
defer c.refreshLock.Unlock()
@@ -632,8 +668,7 @@ func (c *namespaceCache) getNamespaceByID(
632668
entry.RUnlock()
633669
return result, nil
634670
}
635-
// impossible case
636-
return nil, serviceerror.NewInternal("namespaceCache encounter case where namespace exists but cannot be loaded")
671+
return nil, serviceerror.NewNotFound(fmt.Sprintf("namespace ID: %v not found", id))
637672
}
638673

639674
func (c *namespaceCache) triggerNamespaceChangePrepareCallbackLocked() {

common/cache/namespaceCache_test.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ func (s *namespaceCacheSuite) TestRegisterCallback_CatchUp() {
353353
s.Nil(s.namespaceCache.refreshNamespaces())
354354

355355
prepareCallbacckInvoked := false
356-
entriesNotification := []*NamespaceCacheEntry{}
356+
var entriesNotification []*NamespaceCacheEntry
357357
// we are not testing catching up, so make this really large
358358
currentNamespaceNotificationVersion := int64(0)
359359
s.namespaceCache.RegisterNamespaceChangeCallback(
@@ -480,8 +480,8 @@ func (s *namespaceCacheSuite) TestUpdateCache_TriggerCallBack() {
480480
namespaceNotificationVersion++
481481

482482
prepareCallbacckInvoked := false
483-
entriesOld := []*NamespaceCacheEntry{}
484-
entriesNew := []*NamespaceCacheEntry{}
483+
var entriesOld []*NamespaceCacheEntry
484+
var entriesNew []*NamespaceCacheEntry
485485
// we are not testing catching up, so make this really large
486486
currentNamespaceNotificationVersion := int64(9999999)
487487
s.namespaceCache.RegisterNamespaceChangeCallback(
@@ -559,12 +559,26 @@ func (s *namespaceCacheSuite) TestGetTriggerListAndUpdateCache_ConcurrentAccess(
559559
testGetFn := func() {
560560
<-startChan
561561
entryNew, err := s.namespaceCache.GetNamespaceByID(id)
562-
s.Nil(err)
563-
// make the config version the same so we can easily compare those
564-
entryNew.configVersion = 0
565-
entryNew.failoverVersion = 0
566-
s.Equal(entryOld, entryNew)
567-
waitGroup.Done()
562+
switch err.(type) {
563+
case nil:
564+
// make the config version the same so we can easily compare those
565+
entryNew.configVersion = 0
566+
entryNew.failoverVersion = 0
567+
s.Equal(entryOld, entryNew)
568+
waitGroup.Done()
569+
case *serviceerror.NotFound:
570+
time.Sleep(2 * NamespaceCacheMinRefreshInterval)
571+
entryNew, err := s.namespaceCache.GetNamespaceByID(id)
572+
s.NoError(err)
573+
// make the config version the same so we can easily compare those
574+
entryNew.configVersion = 0
575+
entryNew.failoverVersion = 0
576+
s.Equal(entryOld, entryNew)
577+
waitGroup.Done()
578+
default:
579+
s.NoError(err)
580+
waitGroup.Done()
581+
}
568582
}
569583

570584
for i := 0; i < coroutineCountGet; i++ {

0 commit comments

Comments
 (0)