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 }