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 }