mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 05:27:21 +00:00
cacher: replace usable lock with conditional variable
This commit is contained in:
parent
11211a49e3
commit
aa472ff734
@ -72,19 +72,13 @@ type CacherConfig struct {
|
|||||||
type Cacher struct {
|
type Cacher struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
// Each user-facing method that is not simply redirected to the underlying
|
// Before accessing the cacher's cache, wait for the ready to be ok.
|
||||||
// storage has to read-lock on this mutex before starting any processing.
|
|
||||||
// This is necessary to prevent users from accessing structures that are
|
// This is necessary to prevent users from accessing structures that are
|
||||||
// uninitialized or are being repopulated right now.
|
// uninitialized or are being repopulated right now.
|
||||||
// NOTE: We cannot easily reuse the main mutex for it due to multi-threaded
|
// ready needs to be set to false when the cacher is paused or stopped.
|
||||||
// interactions of Cacher with the underlying WatchCache. Since Cacher is
|
// ready needs to be set to true when the cacher is ready to use after
|
||||||
// caling WatchCache directly and WatchCache is calling Cacher methods
|
// initialization.
|
||||||
// via its OnEvent and OnReplace hooks, we explicitly assume that if mutexes
|
ready *ready
|
||||||
// of both structures are held, the one from WatchCache is acquired first
|
|
||||||
// to avoid deadlocks. Unfortunately, forcing this rule in startCaching
|
|
||||||
// would be very difficult and introducing one more mutex seems to be much
|
|
||||||
// easier.
|
|
||||||
usable sync.RWMutex
|
|
||||||
|
|
||||||
// Underlying storage.Interface.
|
// Underlying storage.Interface.
|
||||||
storage Interface
|
storage Interface
|
||||||
@ -126,6 +120,7 @@ func NewCacherFromConfig(config CacherConfig) *Cacher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cacher := &Cacher{
|
cacher := &Cacher{
|
||||||
|
ready: newReady(),
|
||||||
storage: config.Storage,
|
storage: config.Storage,
|
||||||
watchCache: watchCache,
|
watchCache: watchCache,
|
||||||
reflector: cache.NewReflector(listerWatcher, config.Type, watchCache, 0),
|
reflector: cache.NewReflector(listerWatcher, config.Type, watchCache, 0),
|
||||||
@ -139,8 +134,6 @@ func NewCacherFromConfig(config CacherConfig) *Cacher {
|
|||||||
// So we will be simply closing the channel, and synchronizing on the WaitGroup.
|
// So we will be simply closing the channel, and synchronizing on the WaitGroup.
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
// See startCaching method for explanation and where this is unlocked.
|
|
||||||
cacher.usable.Lock()
|
|
||||||
watchCache.SetOnEvent(cacher.processEvent)
|
watchCache.SetOnEvent(cacher.processEvent)
|
||||||
|
|
||||||
stopCh := cacher.stopCh
|
stopCh := cacher.stopCh
|
||||||
@ -168,11 +161,11 @@ func (c *Cacher) startCaching(stopChannel <-chan struct{}) {
|
|||||||
successfulList := false
|
successfulList := false
|
||||||
c.watchCache.SetOnReplace(func() {
|
c.watchCache.SetOnReplace(func() {
|
||||||
successfulList = true
|
successfulList = true
|
||||||
c.usable.Unlock()
|
c.ready.set(true)
|
||||||
})
|
})
|
||||||
defer func() {
|
defer func() {
|
||||||
if successfulList {
|
if successfulList {
|
||||||
c.usable.Lock()
|
c.ready.set(false)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -213,9 +206,7 @@ func (c *Cacher) Watch(ctx context.Context, key string, resourceVersion string,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do NOT allow Watch to start when the underlying structures are not propagated.
|
c.ready.wait()
|
||||||
c.usable.RLock()
|
|
||||||
defer c.usable.RUnlock()
|
|
||||||
|
|
||||||
// We explicitly use thread unsafe version and do locking ourself to ensure that
|
// We explicitly use thread unsafe version and do locking ourself to ensure that
|
||||||
// no new events will be processed in the meantime. The watchCache will be unlocked
|
// no new events will be processed in the meantime. The watchCache will be unlocked
|
||||||
@ -272,13 +263,7 @@ func (c *Cacher) List(ctx context.Context, key string, resourceVersion string, f
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// To avoid situation when List is processed before the underlying
|
c.ready.wait()
|
||||||
// watchCache is propagated for the first time, we acquire and immediately
|
|
||||||
// release the 'usable' lock.
|
|
||||||
// We don't need to hold it all the time, because watchCache is thread-safe
|
|
||||||
// and it would complicate already very difficult locking pattern.
|
|
||||||
c.usable.RLock()
|
|
||||||
c.usable.RUnlock()
|
|
||||||
|
|
||||||
// List elements from cache, with at least 'listRV'.
|
// List elements from cache, with at least 'listRV'.
|
||||||
listPtr, err := meta.GetItemsPtr(listObj)
|
listPtr, err := meta.GetItemsPtr(listObj)
|
||||||
@ -381,18 +366,13 @@ func filterFunction(key string, keyFunc func(runtime.Object) (string, error), fi
|
|||||||
|
|
||||||
// Returns resource version to which the underlying cache is synced.
|
// Returns resource version to which the underlying cache is synced.
|
||||||
func (c *Cacher) LastSyncResourceVersion() (uint64, error) {
|
func (c *Cacher) LastSyncResourceVersion() (uint64, error) {
|
||||||
// To avoid situation when LastSyncResourceVersion is processed before the
|
c.ready.wait()
|
||||||
// underlying watchCache is propagated, we acquire 'usable' lock.
|
|
||||||
c.usable.RLock()
|
|
||||||
defer c.usable.RUnlock()
|
|
||||||
|
|
||||||
c.RLock()
|
|
||||||
defer c.RUnlock()
|
|
||||||
|
|
||||||
resourceVersion := c.reflector.LastSyncResourceVersion()
|
resourceVersion := c.reflector.LastSyncResourceVersion()
|
||||||
if resourceVersion == "" {
|
if resourceVersion == "" {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return strconv.ParseUint(resourceVersion, 10, 64)
|
return strconv.ParseUint(resourceVersion, 10, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,3 +571,27 @@ func (c *cacheWatcher) process(initEvents []watchCacheEvent, resourceVersion uin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ready struct {
|
||||||
|
ok bool
|
||||||
|
c *sync.Cond
|
||||||
|
}
|
||||||
|
|
||||||
|
func newReady() *ready {
|
||||||
|
return &ready{c: sync.NewCond(&sync.Mutex{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ready) wait() {
|
||||||
|
r.c.L.Lock()
|
||||||
|
for !r.ok {
|
||||||
|
r.c.Wait()
|
||||||
|
}
|
||||||
|
r.c.L.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ready) set(ok bool) {
|
||||||
|
r.c.L.Lock()
|
||||||
|
defer r.c.L.Unlock()
|
||||||
|
r.ok = ok
|
||||||
|
r.c.Broadcast()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user