k8s.io/apiserver@v0.29.3/pkg/storage/cacher/watch_cache_interval.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cacher
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  
    23  	"k8s.io/apimachinery/pkg/fields"
    24  	"k8s.io/apimachinery/pkg/labels"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/apimachinery/pkg/watch"
    27  	"k8s.io/client-go/tools/cache"
    28  )
    29  
    30  // watchCacheInterval serves as an abstraction over a source
    31  // of watchCacheEvents. It maintains a window of events over
    32  // an underlying source and these events can be served using
    33  // the exposed Next() API. The main intent for doing things
    34  // this way is to introduce an upper bound of memory usage
    35  // for starting a watch and reduce the maximum possible time
    36  // interval for which the lock would be held while events are
    37  // copied over.
    38  //
    39  // The source of events for the interval is typically either
    40  // the watchCache circular buffer, if events being retrieved
    41  // need to be for resource versions > 0 or the underlying
    42  // implementation of Store, if resource version = 0.
    43  //
    44  // Furthermore, an interval can be either valid or invalid at
    45  // any given point of time. The notion of validity makes sense
    46  // only in cases where the window of events in the underlying
    47  // source can change over time - i.e. for watchCache circular
    48  // buffer. When the circular buffer is full and an event needs
    49  // to be popped off, watchCache::startIndex is incremented. In
    50  // this case, an interval tracking that popped event is valid
    51  // only if it has already been copied to its internal buffer.
    52  // However, for efficiency we perform that lazily and we mark
    53  // an interval as invalid iff we need to copy events from the
    54  // watchCache and we end up needing events that have already
    55  // been popped off. This translates to the following condition:
    56  //
    57  //	watchCacheInterval::startIndex >= watchCache::startIndex.
    58  //
    59  // When this condition becomes false, the interval is no longer
    60  // valid and should not be used to retrieve and serve elements
    61  // from the underlying source.
    62  type watchCacheInterval struct {
    63  	// startIndex denotes the starting point of the interval
    64  	// being considered. The value is the index in the actual
    65  	// source of watchCacheEvents. If the source of events is
    66  	// the watchCache, then this must be used modulo capacity.
    67  	startIndex int
    68  
    69  	// endIndex denotes the ending point of the interval being
    70  	// considered. The value is the index in the actual source
    71  	// of events. If the source of the events is the watchCache,
    72  	// then this should be used modulo capacity.
    73  	endIndex int
    74  
    75  	// indexer is meant to inject behaviour for how an event must
    76  	// be retrieved from the underlying source given an index.
    77  	indexer indexerFunc
    78  
    79  	// indexValidator is used to check if a given index is still
    80  	// valid perspective. If it is deemed that the index is not
    81  	// valid, then this interval can no longer be used to serve
    82  	// events. Use of indexValidator is warranted only in cases
    83  	// where the window of events in the underlying source can
    84  	// change over time. Furthermore, an interval is invalid if
    85  	// its startIndex no longer coincides with the startIndex of
    86  	// underlying source.
    87  	indexValidator indexValidator
    88  
    89  	// buffer holds watchCacheEvents that this interval returns on
    90  	// a call to Next(). This exists mainly to reduce acquiring the
    91  	// lock on each invocation of Next().
    92  	buffer *watchCacheIntervalBuffer
    93  
    94  	// lock effectively protects access to the underlying source
    95  	// of events through - indexer and indexValidator.
    96  	//
    97  	// Given that indexer and indexValidator only read state, if
    98  	// possible, Locker obtained through RLocker() is provided.
    99  	lock sync.Locker
   100  }
   101  
   102  type attrFunc func(runtime.Object) (labels.Set, fields.Set, error)
   103  type indexerFunc func(int) *watchCacheEvent
   104  type indexValidator func(int) bool
   105  
   106  func newCacheInterval(startIndex, endIndex int, indexer indexerFunc, indexValidator indexValidator, locker sync.Locker) *watchCacheInterval {
   107  	return &watchCacheInterval{
   108  		startIndex:     startIndex,
   109  		endIndex:       endIndex,
   110  		indexer:        indexer,
   111  		indexValidator: indexValidator,
   112  		buffer:         &watchCacheIntervalBuffer{buffer: make([]*watchCacheEvent, bufferSize)},
   113  		lock:           locker,
   114  	}
   115  }
   116  
   117  // newCacheIntervalFromStore is meant to handle the case of rv=0, such that the events
   118  // returned by Next() need to be events from a List() done on the underlying store of
   119  // the watch cache.
   120  func newCacheIntervalFromStore(resourceVersion uint64, store cache.Indexer, getAttrsFunc attrFunc) (*watchCacheInterval, error) {
   121  	buffer := &watchCacheIntervalBuffer{}
   122  	allItems := store.List()
   123  	buffer.buffer = make([]*watchCacheEvent, len(allItems))
   124  	for i, item := range allItems {
   125  		elem, ok := item.(*storeElement)
   126  		if !ok {
   127  			return nil, fmt.Errorf("not a storeElement: %v", elem)
   128  		}
   129  		objLabels, objFields, err := getAttrsFunc(elem.Object)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  		buffer.buffer[i] = &watchCacheEvent{
   134  			Type:            watch.Added,
   135  			Object:          elem.Object,
   136  			ObjLabels:       objLabels,
   137  			ObjFields:       objFields,
   138  			Key:             elem.Key,
   139  			ResourceVersion: resourceVersion,
   140  		}
   141  		buffer.endIndex++
   142  	}
   143  	ci := &watchCacheInterval{
   144  		startIndex: 0,
   145  		// Simulate that we already have all the events we're looking for.
   146  		endIndex: 0,
   147  		buffer:   buffer,
   148  	}
   149  
   150  	return ci, nil
   151  }
   152  
   153  // Next returns the next item in the cache interval provided the cache
   154  // interval is still valid. An error is returned if the interval is
   155  // invalidated.
   156  func (wci *watchCacheInterval) Next() (*watchCacheEvent, error) {
   157  	// if there are items in the buffer to return, return from
   158  	// the buffer.
   159  	if event, exists := wci.buffer.next(); exists {
   160  		return event, nil
   161  	}
   162  	// check if there are still other events in this interval
   163  	// that can be processed.
   164  	if wci.startIndex >= wci.endIndex {
   165  		return nil, nil
   166  	}
   167  	wci.lock.Lock()
   168  	defer wci.lock.Unlock()
   169  
   170  	if valid := wci.indexValidator(wci.startIndex); !valid {
   171  		return nil, fmt.Errorf("cache interval invalidated, interval startIndex: %d", wci.startIndex)
   172  	}
   173  
   174  	wci.fillBuffer()
   175  	if event, exists := wci.buffer.next(); exists {
   176  		return event, nil
   177  	}
   178  	return nil, nil
   179  }
   180  
   181  func (wci *watchCacheInterval) fillBuffer() {
   182  	wci.buffer.startIndex = 0
   183  	wci.buffer.endIndex = 0
   184  	for wci.startIndex < wci.endIndex && !wci.buffer.isFull() {
   185  		event := wci.indexer(wci.startIndex)
   186  		if event == nil {
   187  			break
   188  		}
   189  		wci.buffer.buffer[wci.buffer.endIndex] = event
   190  		wci.buffer.endIndex++
   191  		wci.startIndex++
   192  	}
   193  }
   194  
   195  const bufferSize = 100
   196  
   197  // watchCacheIntervalBuffer is used to reduce acquiring
   198  // the lock on each invocation of watchCacheInterval.Next().
   199  type watchCacheIntervalBuffer struct {
   200  	// buffer is used to hold watchCacheEvents that
   201  	// the interval returns on a call to Next().
   202  	buffer []*watchCacheEvent
   203  	// The first element of buffer is defined by startIndex,
   204  	// its last element is defined by endIndex.
   205  	startIndex int
   206  	endIndex   int
   207  }
   208  
   209  // next returns the next event present in the interval buffer provided
   210  // it is not empty.
   211  func (wcib *watchCacheIntervalBuffer) next() (*watchCacheEvent, bool) {
   212  	if wcib.isEmpty() {
   213  		return nil, false
   214  	}
   215  	next := wcib.buffer[wcib.startIndex]
   216  	wcib.startIndex++
   217  	return next, true
   218  }
   219  
   220  func (wcib *watchCacheIntervalBuffer) isFull() bool {
   221  	return wcib.endIndex >= bufferSize
   222  }
   223  
   224  func (wcib *watchCacheIntervalBuffer) isEmpty() bool {
   225  	return wcib.startIndex == wcib.endIndex
   226  }