k8s.io/apiserver@v0.31.1/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  	"sort"
    22  	"sync"
    23  
    24  	"k8s.io/apimachinery/pkg/fields"
    25  	"k8s.io/apimachinery/pkg/labels"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/watch"
    28  	"k8s.io/client-go/tools/cache"
    29  )
    30  
    31  // watchCacheInterval serves as an abstraction over a source
    32  // of watchCacheEvents. It maintains a window of events over
    33  // an underlying source and these events can be served using
    34  // the exposed Next() API. The main intent for doing things
    35  // this way is to introduce an upper bound of memory usage
    36  // for starting a watch and reduce the maximum possible time
    37  // interval for which the lock would be held while events are
    38  // copied over.
    39  //
    40  // The source of events for the interval is typically either
    41  // the watchCache circular buffer, if events being retrieved
    42  // need to be for resource versions > 0 or the underlying
    43  // implementation of Store, if resource version = 0.
    44  //
    45  // Furthermore, an interval can be either valid or invalid at
    46  // any given point of time. The notion of validity makes sense
    47  // only in cases where the window of events in the underlying
    48  // source can change over time - i.e. for watchCache circular
    49  // buffer. When the circular buffer is full and an event needs
    50  // to be popped off, watchCache::startIndex is incremented. In
    51  // this case, an interval tracking that popped event is valid
    52  // only if it has already been copied to its internal buffer.
    53  // However, for efficiency we perform that lazily and we mark
    54  // an interval as invalid iff we need to copy events from the
    55  // watchCache and we end up needing events that have already
    56  // been popped off. This translates to the following condition:
    57  //
    58  //	watchCacheInterval::startIndex >= watchCache::startIndex.
    59  //
    60  // When this condition becomes false, the interval is no longer
    61  // valid and should not be used to retrieve and serve elements
    62  // from the underlying source.
    63  type watchCacheInterval struct {
    64  	// startIndex denotes the starting point of the interval
    65  	// being considered. The value is the index in the actual
    66  	// source of watchCacheEvents. If the source of events is
    67  	// the watchCache, then this must be used modulo capacity.
    68  	startIndex int
    69  
    70  	// endIndex denotes the ending point of the interval being
    71  	// considered. The value is the index in the actual source
    72  	// of events. If the source of the events is the watchCache,
    73  	// then this should be used modulo capacity.
    74  	endIndex int
    75  
    76  	// indexer is meant to inject behaviour for how an event must
    77  	// be retrieved from the underlying source given an index.
    78  	indexer indexerFunc
    79  
    80  	// indexValidator is used to check if a given index is still
    81  	// valid perspective. If it is deemed that the index is not
    82  	// valid, then this interval can no longer be used to serve
    83  	// events. Use of indexValidator is warranted only in cases
    84  	// where the window of events in the underlying source can
    85  	// change over time. Furthermore, an interval is invalid if
    86  	// its startIndex no longer coincides with the startIndex of
    87  	// underlying source.
    88  	indexValidator indexValidator
    89  
    90  	// buffer holds watchCacheEvents that this interval returns on
    91  	// a call to Next(). This exists mainly to reduce acquiring the
    92  	// lock on each invocation of Next().
    93  	buffer *watchCacheIntervalBuffer
    94  
    95  	// lock effectively protects access to the underlying source
    96  	// of events through - indexer and indexValidator.
    97  	//
    98  	// Given that indexer and indexValidator only read state, if
    99  	// possible, Locker obtained through RLocker() is provided.
   100  	lock sync.Locker
   101  }
   102  
   103  type attrFunc func(runtime.Object) (labels.Set, fields.Set, error)
   104  type indexerFunc func(int) *watchCacheEvent
   105  type indexValidator func(int) bool
   106  
   107  func newCacheInterval(startIndex, endIndex int, indexer indexerFunc, indexValidator indexValidator, locker sync.Locker) *watchCacheInterval {
   108  	return &watchCacheInterval{
   109  		startIndex:     startIndex,
   110  		endIndex:       endIndex,
   111  		indexer:        indexer,
   112  		indexValidator: indexValidator,
   113  		buffer:         &watchCacheIntervalBuffer{buffer: make([]*watchCacheEvent, bufferSize)},
   114  		lock:           locker,
   115  	}
   116  }
   117  
   118  type sortableWatchCacheEvents []*watchCacheEvent
   119  
   120  func (s sortableWatchCacheEvents) Len() int {
   121  	return len(s)
   122  }
   123  
   124  func (s sortableWatchCacheEvents) Less(i, j int) bool {
   125  	return s[i].Key < s[j].Key
   126  }
   127  
   128  func (s sortableWatchCacheEvents) Swap(i, j int) {
   129  	s[i], s[j] = s[j], s[i]
   130  }
   131  
   132  // newCacheIntervalFromStore is meant to handle the case of rv=0, such that the events
   133  // returned by Next() need to be events from a List() done on the underlying store of
   134  // the watch cache.
   135  // The items returned in the interval will be sorted by Key.
   136  func newCacheIntervalFromStore(resourceVersion uint64, store cache.Indexer, getAttrsFunc attrFunc, key string, matchesSingle bool) (*watchCacheInterval, error) {
   137  	buffer := &watchCacheIntervalBuffer{}
   138  	var allItems []interface{}
   139  
   140  	if matchesSingle {
   141  		item, exists, err := store.GetByKey(key)
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  
   146  		if exists {
   147  			allItems = append(allItems, item)
   148  		}
   149  	} else {
   150  		allItems = store.List()
   151  	}
   152  	buffer.buffer = make([]*watchCacheEvent, len(allItems))
   153  	for i, item := range allItems {
   154  		elem, ok := item.(*storeElement)
   155  		if !ok {
   156  			return nil, fmt.Errorf("not a storeElement: %v", elem)
   157  		}
   158  		objLabels, objFields, err := getAttrsFunc(elem.Object)
   159  		if err != nil {
   160  			return nil, err
   161  		}
   162  		buffer.buffer[i] = &watchCacheEvent{
   163  			Type:            watch.Added,
   164  			Object:          elem.Object,
   165  			ObjLabels:       objLabels,
   166  			ObjFields:       objFields,
   167  			Key:             elem.Key,
   168  			ResourceVersion: resourceVersion,
   169  		}
   170  		buffer.endIndex++
   171  	}
   172  	sort.Sort(sortableWatchCacheEvents(buffer.buffer))
   173  	ci := &watchCacheInterval{
   174  		startIndex: 0,
   175  		// Simulate that we already have all the events we're looking for.
   176  		endIndex: 0,
   177  		buffer:   buffer,
   178  	}
   179  
   180  	return ci, nil
   181  }
   182  
   183  // Next returns the next item in the cache interval provided the cache
   184  // interval is still valid. An error is returned if the interval is
   185  // invalidated.
   186  func (wci *watchCacheInterval) Next() (*watchCacheEvent, error) {
   187  	// if there are items in the buffer to return, return from
   188  	// the buffer.
   189  	if event, exists := wci.buffer.next(); exists {
   190  		return event, nil
   191  	}
   192  	// check if there are still other events in this interval
   193  	// that can be processed.
   194  	if wci.startIndex >= wci.endIndex {
   195  		return nil, nil
   196  	}
   197  	wci.lock.Lock()
   198  	defer wci.lock.Unlock()
   199  
   200  	if valid := wci.indexValidator(wci.startIndex); !valid {
   201  		return nil, fmt.Errorf("cache interval invalidated, interval startIndex: %d", wci.startIndex)
   202  	}
   203  
   204  	wci.fillBuffer()
   205  	if event, exists := wci.buffer.next(); exists {
   206  		return event, nil
   207  	}
   208  	return nil, nil
   209  }
   210  
   211  func (wci *watchCacheInterval) fillBuffer() {
   212  	wci.buffer.startIndex = 0
   213  	wci.buffer.endIndex = 0
   214  	for wci.startIndex < wci.endIndex && !wci.buffer.isFull() {
   215  		event := wci.indexer(wci.startIndex)
   216  		if event == nil {
   217  			break
   218  		}
   219  		wci.buffer.buffer[wci.buffer.endIndex] = event
   220  		wci.buffer.endIndex++
   221  		wci.startIndex++
   222  	}
   223  }
   224  
   225  const bufferSize = 100
   226  
   227  // watchCacheIntervalBuffer is used to reduce acquiring
   228  // the lock on each invocation of watchCacheInterval.Next().
   229  type watchCacheIntervalBuffer struct {
   230  	// buffer is used to hold watchCacheEvents that
   231  	// the interval returns on a call to Next().
   232  	buffer []*watchCacheEvent
   233  	// The first element of buffer is defined by startIndex,
   234  	// its last element is defined by endIndex.
   235  	startIndex int
   236  	endIndex   int
   237  }
   238  
   239  // next returns the next event present in the interval buffer provided
   240  // it is not empty.
   241  func (wcib *watchCacheIntervalBuffer) next() (*watchCacheEvent, bool) {
   242  	if wcib.isEmpty() {
   243  		return nil, false
   244  	}
   245  	next := wcib.buffer[wcib.startIndex]
   246  	wcib.startIndex++
   247  	return next, true
   248  }
   249  
   250  func (wcib *watchCacheIntervalBuffer) isFull() bool {
   251  	return wcib.endIndex >= bufferSize
   252  }
   253  
   254  func (wcib *watchCacheIntervalBuffer) isEmpty() bool {
   255  	return wcib.startIndex == wcib.endIndex
   256  }