feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
committed by
GitHub
parent
b5015ec7b9
commit
447a51f08b
534
vendor/k8s.io/apiserver/pkg/storage/cacher/cache_watcher.go
generated
vendored
Normal file
534
vendor/k8s.io/apiserver/pkg/storage/cacher/cache_watcher.go
generated
vendored
Normal file
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
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 cacher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/cacher/metrics"
|
||||
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// possible states of the cache watcher
|
||||
const (
|
||||
// cacheWatcherWaitingForBookmark indicates the cacher
|
||||
// is waiting for a bookmark event with a specific RV set
|
||||
cacheWatcherWaitingForBookmark = iota
|
||||
|
||||
// cacheWatcherBookmarkReceived indicates that the cacher
|
||||
// has received a bookmark event with required RV
|
||||
cacheWatcherBookmarkReceived
|
||||
|
||||
// cacheWatcherBookmarkSent indicates that the cacher
|
||||
// has already sent a bookmark event to a client
|
||||
cacheWatcherBookmarkSent
|
||||
)
|
||||
|
||||
// cacheWatcher implements watch.Interface
|
||||
// this is not thread-safe
|
||||
type cacheWatcher struct {
|
||||
input chan *watchCacheEvent
|
||||
result chan watch.Event
|
||||
done chan struct{}
|
||||
filter filterWithAttrsFunc
|
||||
stopped bool
|
||||
forget func(bool)
|
||||
versioner storage.Versioner
|
||||
// The watcher will be closed by server after the deadline,
|
||||
// save it here to send bookmark events before that.
|
||||
deadline time.Time
|
||||
allowWatchBookmarks bool
|
||||
groupResource schema.GroupResource
|
||||
|
||||
// human readable identifier that helps assigning cacheWatcher
|
||||
// instance with request
|
||||
identifier string
|
||||
|
||||
// drainInputBuffer indicates whether we should delay closing this watcher
|
||||
// and send all event in the input buffer.
|
||||
drainInputBuffer bool
|
||||
|
||||
// bookmarkAfterResourceVersion holds an RV that indicates
|
||||
// when we should start delivering bookmark events.
|
||||
// If this field holds the value of 0 that means
|
||||
// we don't have any special preferences toward delivering bookmark events.
|
||||
// Note that this field is used in conjunction with the state field.
|
||||
// It should not be changed once the watcher has been started.
|
||||
bookmarkAfterResourceVersion uint64
|
||||
|
||||
// stateMutex protects state
|
||||
stateMutex sync.Mutex
|
||||
|
||||
// state holds a numeric value indicating the current state of the watcher
|
||||
state int
|
||||
}
|
||||
|
||||
func newCacheWatcher(
|
||||
chanSize int,
|
||||
filter filterWithAttrsFunc,
|
||||
forget func(bool),
|
||||
versioner storage.Versioner,
|
||||
deadline time.Time,
|
||||
allowWatchBookmarks bool,
|
||||
groupResource schema.GroupResource,
|
||||
identifier string,
|
||||
) *cacheWatcher {
|
||||
return &cacheWatcher{
|
||||
input: make(chan *watchCacheEvent, chanSize),
|
||||
result: make(chan watch.Event, chanSize),
|
||||
done: make(chan struct{}),
|
||||
filter: filter,
|
||||
stopped: false,
|
||||
forget: forget,
|
||||
versioner: versioner,
|
||||
deadline: deadline,
|
||||
allowWatchBookmarks: allowWatchBookmarks,
|
||||
groupResource: groupResource,
|
||||
identifier: identifier,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements watch.Interface.
|
||||
func (c *cacheWatcher) ResultChan() <-chan watch.Event {
|
||||
return c.result
|
||||
}
|
||||
|
||||
// Implements watch.Interface.
|
||||
func (c *cacheWatcher) Stop() {
|
||||
c.forget(false)
|
||||
}
|
||||
|
||||
// we rely on the fact that stopLocked is actually protected by Cacher.Lock()
|
||||
func (c *cacheWatcher) stopLocked() {
|
||||
if !c.stopped {
|
||||
c.stopped = true
|
||||
// stop without draining the input channel was requested.
|
||||
if !c.drainInputBuffer {
|
||||
close(c.done)
|
||||
}
|
||||
close(c.input)
|
||||
}
|
||||
|
||||
// Even if the watcher was already stopped, if it previously was
|
||||
// using draining mode and it's not using it now we need to
|
||||
// close the done channel now. Otherwise we could leak the
|
||||
// processing goroutine if it will be trying to put more objects
|
||||
// into result channel, the channel will be full and there will
|
||||
// already be noone on the processing the events on the receiving end.
|
||||
if !c.drainInputBuffer && !c.isDoneChannelClosedLocked() {
|
||||
close(c.done)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cacheWatcher) nonblockingAdd(event *watchCacheEvent) bool {
|
||||
// if the bookmarkAfterResourceVersion hasn't been seen
|
||||
// we will try to deliver a bookmark event every second.
|
||||
// the following check will discard a bookmark event
|
||||
// if it is < than the bookmarkAfterResourceVersion
|
||||
// so that we don't pollute the input channel
|
||||
if event.Type == watch.Bookmark && event.ResourceVersion < c.bookmarkAfterResourceVersion {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case c.input <- event:
|
||||
c.markBookmarkAfterRvAsReceived(event)
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Nil timer means that add will not block (if it can't send event immediately, it will break the watcher)
|
||||
//
|
||||
// Note that bookmark events are never added via the add method only via the nonblockingAdd.
|
||||
// Changing this behaviour will require moving the markBookmarkAfterRvAsReceived method
|
||||
func (c *cacheWatcher) add(event *watchCacheEvent, timer *time.Timer) bool {
|
||||
// Try to send the event immediately, without blocking.
|
||||
if c.nonblockingAdd(event) {
|
||||
return true
|
||||
}
|
||||
|
||||
closeFunc := func() {
|
||||
// This means that we couldn't send event to that watcher.
|
||||
// Since we don't want to block on it infinitely,
|
||||
// we simply terminate it.
|
||||
metrics.TerminatedWatchersCounter.WithLabelValues(c.groupResource.String()).Inc()
|
||||
// This means that we couldn't send event to that watcher.
|
||||
// Since we don't want to block on it infinitely, we simply terminate it.
|
||||
|
||||
// we are graceful = false, when:
|
||||
//
|
||||
// (a) The bookmarkAfterResourceVersionReceived hasn't been received,
|
||||
// we can safely terminate the watcher. Because the client is waiting
|
||||
// for this specific bookmark, and we even haven't received one.
|
||||
// (b) We have seen the bookmarkAfterResourceVersion, and it was sent already to the client.
|
||||
// We can simply terminate the watcher.
|
||||
|
||||
// we are graceful = true, when:
|
||||
//
|
||||
// (a) We have seen a bookmark, but it hasn't been sent to the client yet.
|
||||
// That means we should drain the input buffer which contains
|
||||
// the bookmarkAfterResourceVersion we want. We do that to make progress
|
||||
// as clients can re-establish a new watch with the given RV and receive
|
||||
// further notifications.
|
||||
graceful := func() bool {
|
||||
c.stateMutex.Lock()
|
||||
defer c.stateMutex.Unlock()
|
||||
return c.state == cacheWatcherBookmarkReceived
|
||||
}()
|
||||
klog.V(1).Infof("Forcing %v watcher close due to unresponsiveness: %v. len(c.input) = %v, len(c.result) = %v, graceful = %v", c.groupResource.String(), c.identifier, len(c.input), len(c.result), graceful)
|
||||
c.forget(graceful)
|
||||
}
|
||||
|
||||
if timer == nil {
|
||||
closeFunc()
|
||||
return false
|
||||
}
|
||||
|
||||
// OK, block sending, but only until timer fires.
|
||||
select {
|
||||
case c.input <- event:
|
||||
return true
|
||||
case <-timer.C:
|
||||
closeFunc()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cacheWatcher) nextBookmarkTime(now time.Time, bookmarkFrequency time.Duration) (time.Time, bool) {
|
||||
// We try to send bookmarks:
|
||||
//
|
||||
// (a) right before the watcher timeout - for now we simply set it 2s before
|
||||
// the deadline
|
||||
//
|
||||
// (b) roughly every minute
|
||||
//
|
||||
// (c) immediately when the bookmarkAfterResourceVersion wasn't confirmed
|
||||
// in this scenario the client have already seen (or is in the process of sending)
|
||||
// all initial data and is interested in seeing
|
||||
// a specific RV value (aka. the bookmarkAfterResourceVersion)
|
||||
// since we don't know when the cacher will see the RV we increase frequency
|
||||
//
|
||||
// (b) gives us periodicity if the watch breaks due to unexpected
|
||||
// conditions, (a) ensures that on timeout the watcher is as close to
|
||||
// now as possible - this covers 99% of cases.
|
||||
|
||||
if !c.wasBookmarkAfterRvReceived() {
|
||||
return time.Time{}, true // schedule immediately
|
||||
}
|
||||
|
||||
heartbeatTime := now.Add(bookmarkFrequency)
|
||||
if c.deadline.IsZero() {
|
||||
// Timeout is set by our client libraries (e.g. reflector) as well as defaulted by
|
||||
// apiserver if properly configured. So this shoudln't happen in practice.
|
||||
return heartbeatTime, true
|
||||
}
|
||||
if pretimeoutTime := c.deadline.Add(-2 * time.Second); pretimeoutTime.Before(heartbeatTime) {
|
||||
heartbeatTime = pretimeoutTime
|
||||
}
|
||||
|
||||
if heartbeatTime.Before(now) {
|
||||
return time.Time{}, false
|
||||
}
|
||||
return heartbeatTime, true
|
||||
}
|
||||
|
||||
// wasBookmarkAfterRvReceived same as wasBookmarkAfterRvReceivedLocked just acquires a lock
|
||||
func (c *cacheWatcher) wasBookmarkAfterRvReceived() bool {
|
||||
c.stateMutex.Lock()
|
||||
defer c.stateMutex.Unlock()
|
||||
return c.wasBookmarkAfterRvReceivedLocked()
|
||||
}
|
||||
|
||||
// wasBookmarkAfterRvReceivedLocked checks if the given cacheWatcher
|
||||
// have seen a bookmark event >= bookmarkAfterResourceVersion
|
||||
func (c *cacheWatcher) wasBookmarkAfterRvReceivedLocked() bool {
|
||||
return c.state != cacheWatcherWaitingForBookmark
|
||||
}
|
||||
|
||||
// markBookmarkAfterRvAsReceived indicates that the given cacheWatcher
|
||||
// have seen a bookmark event >= bookmarkAfterResourceVersion
|
||||
func (c *cacheWatcher) markBookmarkAfterRvAsReceived(event *watchCacheEvent) {
|
||||
if event.Type == watch.Bookmark {
|
||||
c.stateMutex.Lock()
|
||||
defer c.stateMutex.Unlock()
|
||||
if c.wasBookmarkAfterRvReceivedLocked() {
|
||||
return
|
||||
}
|
||||
// bookmark events are scheduled by startDispatchingBookmarkEvents method
|
||||
// since we received a bookmark event that means we have
|
||||
// converged towards the expected RV and it is okay to update the state so that
|
||||
// this cacher can be scheduler for a regular bookmark events
|
||||
c.state = cacheWatcherBookmarkReceived
|
||||
}
|
||||
}
|
||||
|
||||
// wasBookmarkAfterRvSentLocked checks if a bookmark event
|
||||
// with an RV >= the bookmarkAfterResourceVersion has been sent by this watcher
|
||||
func (c *cacheWatcher) wasBookmarkAfterRvSentLocked() bool {
|
||||
return c.state == cacheWatcherBookmarkSent
|
||||
}
|
||||
|
||||
// wasBookmarkAfterRvSent same as wasBookmarkAfterRvSentLocked just acquires a lock
|
||||
func (c *cacheWatcher) wasBookmarkAfterRvSent() bool {
|
||||
c.stateMutex.Lock()
|
||||
defer c.stateMutex.Unlock()
|
||||
return c.wasBookmarkAfterRvSentLocked()
|
||||
}
|
||||
|
||||
// markBookmarkAfterRvSent indicates that the given cacheWatcher
|
||||
// have sent a bookmark event with an RV >= the bookmarkAfterResourceVersion
|
||||
//
|
||||
// this function relies on the fact that the nonblockingAdd method
|
||||
// won't admit a bookmark event with an RV < the bookmarkAfterResourceVersion
|
||||
// so the first received bookmark event is considered to match the bookmarkAfterResourceVersion
|
||||
func (c *cacheWatcher) markBookmarkAfterRvSent(event *watchCacheEvent) {
|
||||
// note that bookmark events are not so common so will acquire a lock every ~60 second or so
|
||||
if event.Type == watch.Bookmark {
|
||||
c.stateMutex.Lock()
|
||||
defer c.stateMutex.Unlock()
|
||||
if !c.wasBookmarkAfterRvSentLocked() {
|
||||
c.state = cacheWatcherBookmarkSent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setBookmarkAfterResourceVersion sets the bookmarkAfterResourceVersion and the state associated with it
|
||||
func (c *cacheWatcher) setBookmarkAfterResourceVersion(bookmarkAfterResourceVersion uint64) {
|
||||
state := cacheWatcherWaitingForBookmark
|
||||
if bookmarkAfterResourceVersion == 0 {
|
||||
state = cacheWatcherBookmarkSent // if no specific RV was requested we assume no-op
|
||||
}
|
||||
c.state = state
|
||||
c.bookmarkAfterResourceVersion = bookmarkAfterResourceVersion
|
||||
}
|
||||
|
||||
// setDrainInputBufferLocked if set to true indicates that we should delay closing this watcher
|
||||
// until we send all events residing in the input buffer.
|
||||
func (c *cacheWatcher) setDrainInputBufferLocked(drain bool) {
|
||||
c.drainInputBuffer = drain
|
||||
}
|
||||
|
||||
// isDoneChannelClosed checks if c.done channel is closed
|
||||
func (c *cacheWatcher) isDoneChannelClosedLocked() bool {
|
||||
select {
|
||||
case <-c.done:
|
||||
return true
|
||||
default:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getMutableObject(object runtime.Object) runtime.Object {
|
||||
if _, ok := object.(*cachingObject); ok {
|
||||
// It is safe to return without deep-copy, because the underlying
|
||||
// object will lazily perform deep-copy on the first try to change
|
||||
// any of its fields.
|
||||
return object
|
||||
}
|
||||
return object.DeepCopyObject()
|
||||
}
|
||||
|
||||
func updateResourceVersion(object runtime.Object, versioner storage.Versioner, resourceVersion uint64) {
|
||||
if err := versioner.UpdateObject(object, resourceVersion); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failure to version api object (%d) %#v: %v", resourceVersion, object, err))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cacheWatcher) convertToWatchEvent(event *watchCacheEvent) *watch.Event {
|
||||
if event.Type == watch.Bookmark {
|
||||
e := &watch.Event{Type: watch.Bookmark, Object: event.Object.DeepCopyObject()}
|
||||
if !c.wasBookmarkAfterRvSent() {
|
||||
if err := storage.AnnotateInitialEventsEndBookmark(e.Object); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("error while accessing object's metadata gr: %v, identifier: %v, obj: %#v, err: %v", c.groupResource, c.identifier, e.Object, err))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
curObjPasses := event.Type != watch.Deleted && c.filter(event.Key, event.ObjLabels, event.ObjFields)
|
||||
oldObjPasses := false
|
||||
if event.PrevObject != nil {
|
||||
oldObjPasses = c.filter(event.Key, event.PrevObjLabels, event.PrevObjFields)
|
||||
}
|
||||
if !curObjPasses && !oldObjPasses {
|
||||
// Watcher is not interested in that object.
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case curObjPasses && !oldObjPasses:
|
||||
return &watch.Event{Type: watch.Added, Object: getMutableObject(event.Object)}
|
||||
case curObjPasses && oldObjPasses:
|
||||
return &watch.Event{Type: watch.Modified, Object: getMutableObject(event.Object)}
|
||||
case !curObjPasses && oldObjPasses:
|
||||
// return a delete event with the previous object content, but with the event's resource version
|
||||
oldObj := getMutableObject(event.PrevObject)
|
||||
// We know that if oldObj is cachingObject (which can only be set via
|
||||
// setCachingObjects), its resourceVersion is already set correctly and
|
||||
// we don't need to update it. However, since cachingObject efficiently
|
||||
// handles noop updates, we avoid this microoptimization here.
|
||||
updateResourceVersion(oldObj, c.versioner, event.ResourceVersion)
|
||||
return &watch.Event{Type: watch.Deleted, Object: oldObj}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: sendWatchCacheEvent is assumed to not modify <event> !!!
|
||||
func (c *cacheWatcher) sendWatchCacheEvent(event *watchCacheEvent) {
|
||||
watchEvent := c.convertToWatchEvent(event)
|
||||
if watchEvent == nil {
|
||||
// Watcher is not interested in that object.
|
||||
return
|
||||
}
|
||||
|
||||
// We need to ensure that if we put event X to the c.result, all
|
||||
// previous events were already put into it before, no matter whether
|
||||
// c.done is close or not.
|
||||
// Thus we cannot simply select from c.done and c.result and this
|
||||
// would give us non-determinism.
|
||||
// At the same time, we don't want to block infinitely on putting
|
||||
// to c.result, when c.done is already closed.
|
||||
//
|
||||
// This ensures that with c.done already close, we at most once go
|
||||
// into the next select after this. With that, no matter which
|
||||
// statement we choose there, we will deliver only consecutive
|
||||
// events.
|
||||
select {
|
||||
case <-c.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case c.result <- *watchEvent:
|
||||
c.markBookmarkAfterRvSent(event)
|
||||
case <-c.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cacheWatcher) processInterval(ctx context.Context, cacheInterval *watchCacheInterval, resourceVersion uint64) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer close(c.result)
|
||||
defer c.Stop()
|
||||
|
||||
// Check how long we are processing initEvents.
|
||||
// As long as these are not processed, we are not processing
|
||||
// any incoming events, so if it takes long, we may actually
|
||||
// block all watchers for some time.
|
||||
// TODO: From the logs it seems that there happens processing
|
||||
// times even up to 1s which is very long. However, this doesn't
|
||||
// depend that much on the number of initEvents. E.g. from the
|
||||
// 2000-node Kubemark run we have logs like this, e.g.:
|
||||
// ... processing 13862 initEvents took 66.808689ms
|
||||
// ... processing 14040 initEvents took 993.532539ms
|
||||
// We should understand what is blocking us in those cases (e.g.
|
||||
// is it lack of CPU, network, or sth else) and potentially
|
||||
// consider increase size of result buffer in those cases.
|
||||
const initProcessThreshold = 500 * time.Millisecond
|
||||
startTime := time.Now()
|
||||
|
||||
initEventCount := 0
|
||||
for {
|
||||
event, err := cacheInterval.Next()
|
||||
if err != nil {
|
||||
// An error indicates that the cache interval
|
||||
// has been invalidated and can no longer serve
|
||||
// events.
|
||||
//
|
||||
// Initially we considered sending an "out-of-history"
|
||||
// Error event in this case, but because historically
|
||||
// such events weren't sent out of the watchCache, we
|
||||
// decided not to. This is still ok, because on watch
|
||||
// closure, the watcher will try to re-instantiate the
|
||||
// watch and then will get an explicit "out-of-history"
|
||||
// window. There is potential for optimization, but for
|
||||
// now, in order to be on the safe side and not break
|
||||
// custom clients, the cost of it is something that we
|
||||
// are fully accepting.
|
||||
klog.Warningf("couldn't retrieve watch event to serve: %#v", err)
|
||||
return
|
||||
}
|
||||
if event == nil {
|
||||
break
|
||||
}
|
||||
c.sendWatchCacheEvent(event)
|
||||
|
||||
// With some events already sent, update resourceVersion so that
|
||||
// events that were buffered and not yet processed won't be delivered
|
||||
// to this watcher second time causing going back in time.
|
||||
//
|
||||
// There is one case where events are not necessary ordered by
|
||||
// resourceVersion, being a case of watching from resourceVersion=0,
|
||||
// which at the beginning returns the state of each objects.
|
||||
// For the purpose of it, we need to max it with the resource version
|
||||
// that we have so far.
|
||||
if event.ResourceVersion > resourceVersion {
|
||||
resourceVersion = event.ResourceVersion
|
||||
}
|
||||
initEventCount++
|
||||
}
|
||||
|
||||
if initEventCount > 0 {
|
||||
metrics.InitCounter.WithLabelValues(c.groupResource.String()).Add(float64(initEventCount))
|
||||
}
|
||||
processingTime := time.Since(startTime)
|
||||
if processingTime > initProcessThreshold {
|
||||
klog.V(2).Infof("processing %d initEvents of %s (%s) took %v", initEventCount, c.groupResource, c.identifier, processingTime)
|
||||
}
|
||||
|
||||
c.process(ctx, resourceVersion)
|
||||
}
|
||||
|
||||
func (c *cacheWatcher) process(ctx context.Context, resourceVersion uint64) {
|
||||
// At this point we already start processing incoming watch events.
|
||||
// However, the init event can still be processed because their serialization
|
||||
// and sending to the client happens asynchrnously.
|
||||
// TODO: As describe in the KEP, we would like to estimate that by delaying
|
||||
// the initialization signal proportionally to the number of events to
|
||||
// process, but we're leaving this to the tuning phase.
|
||||
utilflowcontrol.WatchInitialized(ctx)
|
||||
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-c.input:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// only send events newer than resourceVersion
|
||||
// or a bookmark event with an RV equal to resourceVersion
|
||||
// if we haven't sent one to the client
|
||||
if event.ResourceVersion > resourceVersion || (event.Type == watch.Bookmark && event.ResourceVersion == resourceVersion && !c.wasBookmarkAfterRvSent()) {
|
||||
c.sendWatchCacheEvent(event)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
709
vendor/k8s.io/apiserver/pkg/storage/cacher/cacher.go
generated
vendored
709
vendor/k8s.io/apiserver/pkg/storage/cacher/cacher.go
generated
vendored
File diff suppressed because it is too large
Load Diff
4
vendor/k8s.io/apiserver/pkg/storage/cacher/caching_object.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/storage/cacher/caching_object.go
generated
vendored
@@ -148,6 +148,10 @@ func (o *cachingObject) CacheEncode(id runtime.Identifier, encode func(runtime.O
|
||||
if result.err != nil {
|
||||
return result.err
|
||||
}
|
||||
if b, support := w.(runtime.Splice); support {
|
||||
b.Splice(result.raw)
|
||||
return nil
|
||||
}
|
||||
_, err := w.Write(result.raw)
|
||||
return err
|
||||
}
|
||||
|
||||
77
vendor/k8s.io/apiserver/pkg/storage/cacher/lister_watcher.go
generated
vendored
Normal file
77
vendor/k8s.io/apiserver/pkg/storage/cacher/lister_watcher.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
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 cacher
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// listerWatcher opaques storage.Interface to expose cache.ListerWatcher.
|
||||
type listerWatcher struct {
|
||||
storage storage.Interface
|
||||
resourcePrefix string
|
||||
newListFunc func() runtime.Object
|
||||
}
|
||||
|
||||
// NewListerWatcher returns a storage.Interface backed ListerWatcher.
|
||||
func NewListerWatcher(storage storage.Interface, resourcePrefix string, newListFunc func() runtime.Object) cache.ListerWatcher {
|
||||
return &listerWatcher{
|
||||
storage: storage,
|
||||
resourcePrefix: resourcePrefix,
|
||||
newListFunc: newListFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements cache.ListerWatcher interface.
|
||||
func (lw *listerWatcher) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
list := lw.newListFunc()
|
||||
pred := storage.SelectionPredicate{
|
||||
Label: labels.Everything(),
|
||||
Field: fields.Everything(),
|
||||
Limit: options.Limit,
|
||||
Continue: options.Continue,
|
||||
}
|
||||
|
||||
storageOpts := storage.ListOptions{
|
||||
ResourceVersionMatch: options.ResourceVersionMatch,
|
||||
Predicate: pred,
|
||||
Recursive: true,
|
||||
}
|
||||
if err := lw.storage.GetList(context.TODO(), lw.resourcePrefix, storageOpts, list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Implements cache.ListerWatcher interface.
|
||||
func (lw *listerWatcher) Watch(options metav1.ListOptions) (watch.Interface, error) {
|
||||
opts := storage.ListOptions{
|
||||
ResourceVersion: options.ResourceVersion,
|
||||
Predicate: storage.Everything,
|
||||
Recursive: true,
|
||||
ProgressNotify: true,
|
||||
}
|
||||
return lw.storage.Watch(context.TODO(), lw.resourcePrefix, opts)
|
||||
}
|
||||
14
vendor/k8s.io/apiserver/pkg/storage/cacher/metrics/metrics.go
generated
vendored
14
vendor/k8s.io/apiserver/pkg/storage/cacher/metrics/metrics.go
generated
vendored
@@ -74,6 +74,17 @@ var (
|
||||
[]string{"resource"},
|
||||
)
|
||||
|
||||
EventsReceivedCounter = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "events_received_total",
|
||||
Help: "Counter of events received in watch cache broken by resource type.",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"resource"},
|
||||
)
|
||||
|
||||
EventsCounter = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
@@ -147,6 +158,7 @@ func Register() {
|
||||
legacyregistry.MustRegister(listCacheNumFetched)
|
||||
legacyregistry.MustRegister(listCacheNumReturned)
|
||||
legacyregistry.MustRegister(InitCounter)
|
||||
legacyregistry.MustRegister(EventsReceivedCounter)
|
||||
legacyregistry.MustRegister(EventsCounter)
|
||||
legacyregistry.MustRegister(TerminatedWatchersCounter)
|
||||
legacyregistry.MustRegister(watchCacheCapacityIncreaseTotal)
|
||||
@@ -167,7 +179,7 @@ func RecordListCacheMetrics(resourcePrefix, indexName string, numFetched, numRet
|
||||
func RecordsWatchCacheCapacityChange(objType string, old, new int) {
|
||||
WatchCacheCapacity.WithLabelValues(objType).Set(float64(new))
|
||||
if old < new {
|
||||
WatchCacheCapacity.WithLabelValues(objType).Inc()
|
||||
watchCacheCapacityIncreaseTotal.WithLabelValues(objType).Inc()
|
||||
return
|
||||
}
|
||||
watchCacheCapacityDecreaseTotal.WithLabelValues(objType).Inc()
|
||||
|
||||
125
vendor/k8s.io/apiserver/pkg/storage/cacher/ready.go
generated
vendored
125
vendor/k8s.io/apiserver/pkg/storage/cacher/ready.go
generated
vendored
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package cacher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
@@ -30,67 +31,127 @@ const (
|
||||
)
|
||||
|
||||
// ready is a three state condition variable that blocks until is Ready if is not Stopped.
|
||||
// Its initial state is Pending.
|
||||
// Its initial state is Pending and its state machine diagram is as follow.
|
||||
//
|
||||
// Pending <------> Ready -----> Stopped
|
||||
//
|
||||
// | ^
|
||||
// └---------------------------┘
|
||||
type ready struct {
|
||||
state status
|
||||
c *sync.Cond
|
||||
state status // represent the state of the variable
|
||||
generation int // represent the number of times we have transtioned to ready
|
||||
lock sync.RWMutex // protect the state and generation variables
|
||||
restartLock sync.Mutex // protect the transition from ready to pending where the channel is recreated
|
||||
waitCh chan struct{} // blocks until is ready or stopped
|
||||
}
|
||||
|
||||
func newReady() *ready {
|
||||
return &ready{
|
||||
c: sync.NewCond(&sync.RWMutex{}),
|
||||
state: Pending,
|
||||
waitCh: make(chan struct{}),
|
||||
state: Pending,
|
||||
}
|
||||
}
|
||||
|
||||
// done close the channel once the state is Ready or Stopped
|
||||
func (r *ready) done() chan struct{} {
|
||||
r.restartLock.Lock()
|
||||
defer r.restartLock.Unlock()
|
||||
return r.waitCh
|
||||
}
|
||||
|
||||
// wait blocks until it is Ready or Stopped, it returns an error if is Stopped.
|
||||
func (r *ready) wait() error {
|
||||
r.c.L.Lock()
|
||||
defer r.c.L.Unlock()
|
||||
for r.state == Pending {
|
||||
r.c.Wait()
|
||||
}
|
||||
switch r.state {
|
||||
case Ready:
|
||||
return nil
|
||||
case Stopped:
|
||||
return fmt.Errorf("apiserver cacher is stopped")
|
||||
default:
|
||||
return fmt.Errorf("unexpected apiserver cache state: %v", r.state)
|
||||
func (r *ready) wait(ctx context.Context) error {
|
||||
_, err := r.waitAndReadGeneration(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// waitAndReadGenration blocks until it is Ready or Stopped and returns number
|
||||
// of times we entered ready state if Ready and error otherwise.
|
||||
func (r *ready) waitAndReadGeneration(ctx context.Context) (int, error) {
|
||||
for {
|
||||
// r.done() only blocks if state is Pending
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return 0, ctx.Err()
|
||||
case <-r.done():
|
||||
}
|
||||
|
||||
r.lock.RLock()
|
||||
switch r.state {
|
||||
case Pending:
|
||||
// since we allow to switch between the states Pending and Ready
|
||||
// if there is a quick transition from Pending -> Ready -> Pending
|
||||
// a process that was waiting can get unblocked and see a Pending
|
||||
// state again. If the state is Pending we have to wait again to
|
||||
// avoid an inconsistent state on the system, with some processes not
|
||||
// waiting despite the state moved back to Pending.
|
||||
r.lock.RUnlock()
|
||||
case Ready:
|
||||
generation := r.generation
|
||||
r.lock.RUnlock()
|
||||
return generation, nil
|
||||
case Stopped:
|
||||
r.lock.RUnlock()
|
||||
return 0, fmt.Errorf("apiserver cacher is stopped")
|
||||
default:
|
||||
r.lock.RUnlock()
|
||||
return 0, fmt.Errorf("unexpected apiserver cache state: %v", r.state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check returns true only if it is Ready.
|
||||
func (r *ready) check() bool {
|
||||
// TODO: Make check() function more sophisticated, in particular
|
||||
// allow it to behave as "waitWithTimeout".
|
||||
rwMutex := r.c.L.(*sync.RWMutex)
|
||||
rwMutex.RLock()
|
||||
defer rwMutex.RUnlock()
|
||||
return r.state == Ready
|
||||
_, ok := r.checkAndReadGeneration()
|
||||
return ok
|
||||
}
|
||||
|
||||
// checkAndReadGeneration returns the current generation and whether it is Ready.
|
||||
func (r *ready) checkAndReadGeneration() (int, bool) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
return r.generation, r.state == Ready
|
||||
}
|
||||
|
||||
// set the state to Pending (false) or Ready (true), it does not have effect if the state is Stopped.
|
||||
func (r *ready) set(ok bool) {
|
||||
r.c.L.Lock()
|
||||
defer r.c.L.Unlock()
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
if r.state == Stopped {
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
if ok && r.state == Pending {
|
||||
r.state = Ready
|
||||
} else {
|
||||
r.generation++
|
||||
select {
|
||||
case <-r.waitCh:
|
||||
default:
|
||||
close(r.waitCh)
|
||||
}
|
||||
} else if !ok && r.state == Ready {
|
||||
// creating the waitCh can be racy if
|
||||
// something enter the wait() method
|
||||
select {
|
||||
case <-r.waitCh:
|
||||
r.restartLock.Lock()
|
||||
r.waitCh = make(chan struct{})
|
||||
r.restartLock.Unlock()
|
||||
default:
|
||||
}
|
||||
r.state = Pending
|
||||
}
|
||||
r.c.Broadcast()
|
||||
}
|
||||
|
||||
// stop the condition variable and set it as Stopped. This state is irreversible.
|
||||
func (r *ready) stop() {
|
||||
r.c.L.Lock()
|
||||
defer r.c.L.Unlock()
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
if r.state != Stopped {
|
||||
r.state = Stopped
|
||||
r.c.Broadcast()
|
||||
}
|
||||
select {
|
||||
case <-r.waitCh:
|
||||
default:
|
||||
close(r.waitCh)
|
||||
}
|
||||
}
|
||||
|
||||
115
vendor/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go
generated
vendored
115
vendor/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go
generated
vendored
@@ -30,8 +30,10 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/cacher/metrics"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/klog/v2"
|
||||
@@ -156,14 +158,15 @@ type watchCache struct {
|
||||
// getAttrsFunc is used to get labels and fields of an object.
|
||||
getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error)
|
||||
|
||||
// cache is used a cyclic buffer - its first element (with the smallest
|
||||
// resourceVersion) is defined by startIndex, its last element is defined
|
||||
// by endIndex (if cache is full it will be startIndex + capacity).
|
||||
// Both startIndex and endIndex can be greater than buffer capacity -
|
||||
// you should always apply modulo capacity to get an index in cache array.
|
||||
// cache is used a cyclic buffer - the "current" contents of it are
|
||||
// stored in [start_index%capacity, end_index%capacity) - so the
|
||||
// "current" contents have exactly end_index-start_index items.
|
||||
cache []*watchCacheEvent
|
||||
startIndex int
|
||||
endIndex int
|
||||
// removedEventSinceRelist holds the information whether any of the events
|
||||
// were already removed from the `cache` cyclic buffer since the last relist
|
||||
removedEventSinceRelist bool
|
||||
|
||||
// store will effectively support LIST operation from the "end of cache
|
||||
// history" i.e. from the moment just after the newest cached watched event.
|
||||
@@ -195,6 +198,10 @@ type watchCache struct {
|
||||
|
||||
// For testing cache interval invalidation.
|
||||
indexValidator indexValidator
|
||||
|
||||
// Requests progress notification if there are requests waiting for watch
|
||||
// to be fresh
|
||||
waitingUntilFresh *conditionalProgressRequester
|
||||
}
|
||||
|
||||
func newWatchCache(
|
||||
@@ -203,8 +210,9 @@ func newWatchCache(
|
||||
getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error),
|
||||
versioner storage.Versioner,
|
||||
indexers *cache.Indexers,
|
||||
clock clock.Clock,
|
||||
groupResource schema.GroupResource) *watchCache {
|
||||
clock clock.WithTicker,
|
||||
groupResource schema.GroupResource,
|
||||
progressRequester *conditionalProgressRequester) *watchCache {
|
||||
wc := &watchCache{
|
||||
capacity: defaultLowerBoundCapacity,
|
||||
keyFunc: keyFunc,
|
||||
@@ -221,6 +229,7 @@ func newWatchCache(
|
||||
clock: clock,
|
||||
versioner: versioner,
|
||||
groupResource: groupResource,
|
||||
waitingUntilFresh: progressRequester,
|
||||
}
|
||||
metrics.WatchCacheCapacity.WithLabelValues(groupResource.String()).Set(float64(wc.capacity))
|
||||
wc.cond = sync.NewCond(wc.RLocker())
|
||||
@@ -280,6 +289,8 @@ func (w *watchCache) objectToVersionedRuntimeObject(obj interface{}) (runtime.Ob
|
||||
// processEvent is safe as long as there is at most one call to it in flight
|
||||
// at any point in time.
|
||||
func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, updateFunc func(*storeElement) error) error {
|
||||
metrics.EventsReceivedCounter.WithLabelValues(w.groupResource.String()).Inc()
|
||||
|
||||
key, err := w.keyFunc(event.Object)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't compute key: %v", err)
|
||||
@@ -302,7 +313,7 @@ func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, upd
|
||||
|
||||
if err := func() error {
|
||||
// TODO: We should consider moving this lock below after the watchCacheEvent
|
||||
// is created. In such situation, the only problematic scenario is Replace(
|
||||
// is created. In such situation, the only problematic scenario is Replace()
|
||||
// happening after getting object from store and before acquiring a lock.
|
||||
// Maybe introduce another lock for this purpose.
|
||||
w.Lock()
|
||||
@@ -344,6 +355,7 @@ func (w *watchCache) updateCache(event *watchCacheEvent) {
|
||||
if w.isCacheFullLocked() {
|
||||
// Cache is full - remove the oldest element.
|
||||
w.startIndex++
|
||||
w.removedEventSinceRelist = true
|
||||
}
|
||||
w.cache[w.endIndex%w.capacity] = event
|
||||
w.endIndex++
|
||||
@@ -402,6 +414,7 @@ func (w *watchCache) UpdateResourceVersion(resourceVersion string) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
w.resourceVersion = rv
|
||||
w.cond.Broadcast()
|
||||
}()
|
||||
|
||||
// Avoid calling event handler under lock.
|
||||
@@ -463,25 +476,57 @@ func (w *watchCache) waitUntilFreshAndBlock(ctx context.Context, resourceVersion
|
||||
return nil
|
||||
}
|
||||
|
||||
type sortableStoreElements []interface{}
|
||||
|
||||
func (s sortableStoreElements) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s sortableStoreElements) Less(i, j int) bool {
|
||||
return s[i].(*storeElement).Key < s[j].(*storeElement).Key
|
||||
}
|
||||
|
||||
func (s sortableStoreElements) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// WaitUntilFreshAndList returns list of pointers to `storeElement` objects along
|
||||
// with their ResourceVersion and the name of the index, if any, that was used.
|
||||
func (w *watchCache) WaitUntilFreshAndList(ctx context.Context, resourceVersion uint64, matchValues []storage.MatchValue) ([]interface{}, uint64, string, error) {
|
||||
err := w.waitUntilFreshAndBlock(ctx, resourceVersion)
|
||||
var err error
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.ConsistentListFromCache) && w.notFresh(resourceVersion) {
|
||||
w.waitingUntilFresh.Add()
|
||||
err = w.waitUntilFreshAndBlock(ctx, resourceVersion)
|
||||
w.waitingUntilFresh.Remove()
|
||||
} else {
|
||||
err = w.waitUntilFreshAndBlock(ctx, resourceVersion)
|
||||
}
|
||||
defer w.RUnlock()
|
||||
if err != nil {
|
||||
return nil, 0, "", err
|
||||
}
|
||||
|
||||
// This isn't the place where we do "final filtering" - only some "prefiltering" is happening here. So the only
|
||||
// requirement here is to NOT miss anything that should be returned. We can return as many non-matching items as we
|
||||
// want - they will be filtered out later. The fact that we return less things is only further performance improvement.
|
||||
// TODO: if multiple indexes match, return the one with the fewest items, so as to do as much filtering as possible.
|
||||
for _, matchValue := range matchValues {
|
||||
if result, err := w.store.ByIndex(matchValue.IndexName, matchValue.Value); err == nil {
|
||||
return result, w.resourceVersion, matchValue.IndexName, nil
|
||||
result, rv, index, err := func() ([]interface{}, uint64, string, error) {
|
||||
// This isn't the place where we do "final filtering" - only some "prefiltering" is happening here. So the only
|
||||
// requirement here is to NOT miss anything that should be returned. We can return as many non-matching items as we
|
||||
// want - they will be filtered out later. The fact that we return less things is only further performance improvement.
|
||||
// TODO: if multiple indexes match, return the one with the fewest items, so as to do as much filtering as possible.
|
||||
for _, matchValue := range matchValues {
|
||||
if result, err := w.store.ByIndex(matchValue.IndexName, matchValue.Value); err == nil {
|
||||
return result, w.resourceVersion, matchValue.IndexName, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return w.store.List(), w.resourceVersion, "", nil
|
||||
return w.store.List(), w.resourceVersion, "", nil
|
||||
}()
|
||||
|
||||
sort.Sort(sortableStoreElements(result))
|
||||
return result, rv, index, err
|
||||
}
|
||||
|
||||
func (w *watchCache) notFresh(resourceVersion uint64) bool {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
return resourceVersion > w.resourceVersion
|
||||
}
|
||||
|
||||
// WaitUntilFreshAndGet returns a pointers to <storeElement> object.
|
||||
@@ -551,8 +596,15 @@ func (w *watchCache) Replace(objs []interface{}, resourceVersion string) error {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.startIndex = 0
|
||||
w.endIndex = 0
|
||||
// Ensure startIndex never decreases, so that existing watchCacheInterval
|
||||
// instances get "invalid" errors if the try to download from the buffer
|
||||
// using their own start/end indexes calculated from previous buffer
|
||||
// content.
|
||||
|
||||
// Empty the cyclic buffer, ensuring startIndex doesn't decrease.
|
||||
w.startIndex = w.endIndex
|
||||
w.removedEventSinceRelist = false
|
||||
|
||||
if err := w.store.Replace(toReplace, resourceVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -578,8 +630,8 @@ func (w *watchCache) Resync() error {
|
||||
}
|
||||
|
||||
func (w *watchCache) currentCapacity() int {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
return w.capacity
|
||||
}
|
||||
|
||||
@@ -643,7 +695,7 @@ func (w *watchCache) getAllEventsSinceLocked(resourceVersion uint64) (*watchCach
|
||||
size := w.endIndex - w.startIndex
|
||||
var oldest uint64
|
||||
switch {
|
||||
case w.listResourceVersion > 0 && w.startIndex == 0:
|
||||
case w.listResourceVersion > 0 && !w.removedEventSinceRelist:
|
||||
// If no event was removed from the buffer since last relist, the oldest watch
|
||||
// event we can deliver is one greater than the resource version of the list.
|
||||
oldest = w.listResourceVersion + 1
|
||||
@@ -665,11 +717,7 @@ func (w *watchCache) getAllEventsSinceLocked(resourceVersion uint64) (*watchCach
|
||||
// current state and only then start watching from that point.
|
||||
//
|
||||
// TODO: In v2 api, we should stop returning the current state - #13969.
|
||||
ci, err := newCacheIntervalFromStore(w.resourceVersion, w.store, w.getAttrsFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ci, nil
|
||||
return w.getIntervalFromStoreLocked()
|
||||
}
|
||||
if resourceVersion < oldest-1 {
|
||||
return nil, errors.NewResourceExpired(fmt.Sprintf("too old resource version: %d (%d)", resourceVersion, oldest-1))
|
||||
@@ -686,3 +734,14 @@ func (w *watchCache) getAllEventsSinceLocked(resourceVersion uint64) (*watchCach
|
||||
ci := newCacheInterval(w.startIndex+first, w.endIndex, indexerFunc, w.indexValidator, &w.RWMutex)
|
||||
return ci, nil
|
||||
}
|
||||
|
||||
// getIntervalFromStoreLocked returns a watchCacheInterval
|
||||
// that covers the entire storage state.
|
||||
// This function assumes to be called under the watchCache lock.
|
||||
func (w *watchCache) getIntervalFromStoreLocked() (*watchCacheInterval, error) {
|
||||
ci, err := newCacheIntervalFromStore(w.resourceVersion, w.store, w.getAttrsFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ci, nil
|
||||
}
|
||||
|
||||
121
vendor/k8s.io/apiserver/pkg/storage/cacher/watch_progress.go
generated
vendored
Normal file
121
vendor/k8s.io/apiserver/pkg/storage/cacher/watch_progress.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
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 cacher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
const (
|
||||
// progressRequestPeriod determines period of requesting progress
|
||||
// from etcd when there is a request waiting for watch cache to be fresh.
|
||||
progressRequestPeriod = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
func newConditionalProgressRequester(requestWatchProgress WatchProgressRequester, clock TickerFactory) *conditionalProgressRequester {
|
||||
pr := &conditionalProgressRequester{
|
||||
clock: clock,
|
||||
requestWatchProgress: requestWatchProgress,
|
||||
}
|
||||
pr.cond = sync.NewCond(pr.mux.RLocker())
|
||||
return pr
|
||||
}
|
||||
|
||||
type WatchProgressRequester func(ctx context.Context) error
|
||||
|
||||
type TickerFactory interface {
|
||||
NewTicker(time.Duration) clock.Ticker
|
||||
}
|
||||
|
||||
// conditionalProgressRequester will request progress notification if there
|
||||
// is a request waiting for watch cache to be fresh.
|
||||
type conditionalProgressRequester struct {
|
||||
clock TickerFactory
|
||||
requestWatchProgress WatchProgressRequester
|
||||
|
||||
mux sync.RWMutex
|
||||
cond *sync.Cond
|
||||
waiting int
|
||||
stopped bool
|
||||
}
|
||||
|
||||
func (pr *conditionalProgressRequester) Run(stopCh <-chan struct{}) {
|
||||
ctx := wait.ContextForChannel(stopCh)
|
||||
go func() {
|
||||
defer utilruntime.HandleCrash()
|
||||
<-stopCh
|
||||
pr.mux.Lock()
|
||||
defer pr.mux.Unlock()
|
||||
pr.stopped = true
|
||||
pr.cond.Signal()
|
||||
}()
|
||||
ticker := pr.clock.NewTicker(progressRequestPeriod)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
stopped := func() bool {
|
||||
pr.mux.RLock()
|
||||
defer pr.mux.RUnlock()
|
||||
for pr.waiting == 0 && !pr.stopped {
|
||||
pr.cond.Wait()
|
||||
}
|
||||
return pr.stopped
|
||||
}()
|
||||
if stopped {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ticker.C():
|
||||
shouldRequest := func() bool {
|
||||
pr.mux.RLock()
|
||||
defer pr.mux.RUnlock()
|
||||
return pr.waiting > 0 && !pr.stopped
|
||||
}()
|
||||
if !shouldRequest {
|
||||
continue
|
||||
}
|
||||
err := pr.requestWatchProgress(ctx)
|
||||
if err != nil {
|
||||
klog.V(4).InfoS("Error requesting bookmark", "err", err)
|
||||
}
|
||||
case <-stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *conditionalProgressRequester) Add() {
|
||||
pr.mux.Lock()
|
||||
defer pr.mux.Unlock()
|
||||
pr.waiting += 1
|
||||
pr.cond.Signal()
|
||||
}
|
||||
|
||||
func (pr *conditionalProgressRequester) Remove() {
|
||||
pr.mux.Lock()
|
||||
defer pr.mux.Unlock()
|
||||
pr.waiting -= 1
|
||||
pr.cond.Signal()
|
||||
}
|
||||
Reference in New Issue
Block a user