k8s.io/apiserver@v0.29.3/pkg/storage/cacher/watch_cache.go (about) 1 /* 2 Copyright 2015 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 "context" 21 "fmt" 22 "math" 23 "sort" 24 "sync" 25 "time" 26 27 "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/fields" 29 "k8s.io/apimachinery/pkg/labels" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/apimachinery/pkg/watch" 33 "k8s.io/apiserver/pkg/features" 34 "k8s.io/apiserver/pkg/storage" 35 "k8s.io/apiserver/pkg/storage/cacher/metrics" 36 utilfeature "k8s.io/apiserver/pkg/util/feature" 37 "k8s.io/client-go/tools/cache" 38 "k8s.io/component-base/tracing" 39 "k8s.io/klog/v2" 40 "k8s.io/utils/clock" 41 ) 42 43 const ( 44 // blockTimeout determines how long we're willing to block the request 45 // to wait for a given resource version to be propagated to cache, 46 // before terminating request and returning Timeout error with retry 47 // after suggestion. 48 blockTimeout = 3 * time.Second 49 50 // resourceVersionTooHighRetrySeconds is the seconds before a operation should be retried by the client 51 // after receiving a 'too high resource version' error. 52 resourceVersionTooHighRetrySeconds = 1 53 54 // eventFreshDuration is time duration of events we want to keep. 55 // We set it to `defaultBookmarkFrequency` plus epsilon to maximize 56 // chances that last bookmark was sent within kept history, at the 57 // same time, minimizing the needed memory usage. 58 eventFreshDuration = 75 * time.Second 59 60 // defaultLowerBoundCapacity is a default value for event cache capacity's lower bound. 61 // TODO: Figure out, to what value we can decreased it. 62 defaultLowerBoundCapacity = 100 63 64 // defaultUpperBoundCapacity should be able to keep eventFreshDuration of history. 65 defaultUpperBoundCapacity = 100 * 1024 66 ) 67 68 // watchCacheEvent is a single "watch event" that is send to users of 69 // watchCache. Additionally to a typical "watch.Event" it contains 70 // the previous value of the object to enable proper filtering in the 71 // upper layers. 72 type watchCacheEvent struct { 73 Type watch.EventType 74 Object runtime.Object 75 ObjLabels labels.Set 76 ObjFields fields.Set 77 PrevObject runtime.Object 78 PrevObjLabels labels.Set 79 PrevObjFields fields.Set 80 Key string 81 ResourceVersion uint64 82 RecordTime time.Time 83 } 84 85 // Computing a key of an object is generally non-trivial (it performs 86 // e.g. validation underneath). Similarly computing object fields and 87 // labels. To avoid computing them multiple times (to serve the event 88 // in different List/Watch requests), in the underlying store we are 89 // keeping structs (key, object, labels, fields). 90 type storeElement struct { 91 Key string 92 Object runtime.Object 93 Labels labels.Set 94 Fields fields.Set 95 } 96 97 func storeElementKey(obj interface{}) (string, error) { 98 elem, ok := obj.(*storeElement) 99 if !ok { 100 return "", fmt.Errorf("not a storeElement: %v", obj) 101 } 102 return elem.Key, nil 103 } 104 105 func storeElementObject(obj interface{}) (runtime.Object, error) { 106 elem, ok := obj.(*storeElement) 107 if !ok { 108 return nil, fmt.Errorf("not a storeElement: %v", obj) 109 } 110 return elem.Object, nil 111 } 112 113 func storeElementIndexFunc(objIndexFunc cache.IndexFunc) cache.IndexFunc { 114 return func(obj interface{}) (strings []string, e error) { 115 seo, err := storeElementObject(obj) 116 if err != nil { 117 return nil, err 118 } 119 return objIndexFunc(seo) 120 } 121 } 122 123 func storeElementIndexers(indexers *cache.Indexers) cache.Indexers { 124 if indexers == nil { 125 return cache.Indexers{} 126 } 127 ret := cache.Indexers{} 128 for indexName, indexFunc := range *indexers { 129 ret[indexName] = storeElementIndexFunc(indexFunc) 130 } 131 return ret 132 } 133 134 // watchCache implements a Store interface. 135 // However, it depends on the elements implementing runtime.Object interface. 136 // 137 // watchCache is a "sliding window" (with a limited capacity) of objects 138 // observed from a watch. 139 type watchCache struct { 140 sync.RWMutex 141 142 // Condition on which lists are waiting for the fresh enough 143 // resource version. 144 cond *sync.Cond 145 146 // Maximum size of history window. 147 capacity int 148 149 // upper bound of capacity since event cache has a dynamic size. 150 upperBoundCapacity int 151 152 // lower bound of capacity since event cache has a dynamic size. 153 lowerBoundCapacity int 154 155 // keyFunc is used to get a key in the underlying storage for a given object. 156 keyFunc func(runtime.Object) (string, error) 157 158 // getAttrsFunc is used to get labels and fields of an object. 159 getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error) 160 161 // cache is used a cyclic buffer - the "current" contents of it are 162 // stored in [start_index%capacity, end_index%capacity) - so the 163 // "current" contents have exactly end_index-start_index items. 164 cache []*watchCacheEvent 165 startIndex int 166 endIndex int 167 // removedEventSinceRelist holds the information whether any of the events 168 // were already removed from the `cache` cyclic buffer since the last relist 169 removedEventSinceRelist bool 170 171 // store will effectively support LIST operation from the "end of cache 172 // history" i.e. from the moment just after the newest cached watched event. 173 // It is necessary to effectively allow clients to start watching at now. 174 // NOTE: We assume that <store> is thread-safe. 175 store cache.Indexer 176 177 // ResourceVersion up to which the watchCache is propagated. 178 resourceVersion uint64 179 180 // ResourceVersion of the last list result (populated via Replace() method). 181 listResourceVersion uint64 182 183 // This handler is run at the end of every successful Replace() method. 184 onReplace func() 185 186 // This handler is run at the end of every Add/Update/Delete method 187 // and additionally gets the previous value of the object. 188 eventHandler func(*watchCacheEvent) 189 190 // for testing timeouts. 191 clock clock.Clock 192 193 // An underlying storage.Versioner. 194 versioner storage.Versioner 195 196 // cacher's group resource 197 groupResource schema.GroupResource 198 199 // For testing cache interval invalidation. 200 indexValidator indexValidator 201 202 // Requests progress notification if there are requests waiting for watch 203 // to be fresh 204 waitingUntilFresh *conditionalProgressRequester 205 } 206 207 func newWatchCache( 208 keyFunc func(runtime.Object) (string, error), 209 eventHandler func(*watchCacheEvent), 210 getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error), 211 versioner storage.Versioner, 212 indexers *cache.Indexers, 213 clock clock.WithTicker, 214 groupResource schema.GroupResource, 215 progressRequester *conditionalProgressRequester) *watchCache { 216 wc := &watchCache{ 217 capacity: defaultLowerBoundCapacity, 218 keyFunc: keyFunc, 219 getAttrsFunc: getAttrsFunc, 220 cache: make([]*watchCacheEvent, defaultLowerBoundCapacity), 221 lowerBoundCapacity: defaultLowerBoundCapacity, 222 upperBoundCapacity: defaultUpperBoundCapacity, 223 startIndex: 0, 224 endIndex: 0, 225 store: cache.NewIndexer(storeElementKey, storeElementIndexers(indexers)), 226 resourceVersion: 0, 227 listResourceVersion: 0, 228 eventHandler: eventHandler, 229 clock: clock, 230 versioner: versioner, 231 groupResource: groupResource, 232 waitingUntilFresh: progressRequester, 233 } 234 metrics.WatchCacheCapacity.WithLabelValues(groupResource.String()).Set(float64(wc.capacity)) 235 wc.cond = sync.NewCond(wc.RLocker()) 236 wc.indexValidator = wc.isIndexValidLocked 237 238 return wc 239 } 240 241 // Add takes runtime.Object as an argument. 242 func (w *watchCache) Add(obj interface{}) error { 243 object, resourceVersion, err := w.objectToVersionedRuntimeObject(obj) 244 if err != nil { 245 return err 246 } 247 event := watch.Event{Type: watch.Added, Object: object} 248 249 f := func(elem *storeElement) error { return w.store.Add(elem) } 250 return w.processEvent(event, resourceVersion, f) 251 } 252 253 // Update takes runtime.Object as an argument. 254 func (w *watchCache) Update(obj interface{}) error { 255 object, resourceVersion, err := w.objectToVersionedRuntimeObject(obj) 256 if err != nil { 257 return err 258 } 259 event := watch.Event{Type: watch.Modified, Object: object} 260 261 f := func(elem *storeElement) error { return w.store.Update(elem) } 262 return w.processEvent(event, resourceVersion, f) 263 } 264 265 // Delete takes runtime.Object as an argument. 266 func (w *watchCache) Delete(obj interface{}) error { 267 object, resourceVersion, err := w.objectToVersionedRuntimeObject(obj) 268 if err != nil { 269 return err 270 } 271 event := watch.Event{Type: watch.Deleted, Object: object} 272 273 f := func(elem *storeElement) error { return w.store.Delete(elem) } 274 return w.processEvent(event, resourceVersion, f) 275 } 276 277 func (w *watchCache) objectToVersionedRuntimeObject(obj interface{}) (runtime.Object, uint64, error) { 278 object, ok := obj.(runtime.Object) 279 if !ok { 280 return nil, 0, fmt.Errorf("obj does not implement runtime.Object interface: %v", obj) 281 } 282 resourceVersion, err := w.versioner.ObjectResourceVersion(object) 283 if err != nil { 284 return nil, 0, err 285 } 286 return object, resourceVersion, nil 287 } 288 289 // processEvent is safe as long as there is at most one call to it in flight 290 // at any point in time. 291 func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, updateFunc func(*storeElement) error) error { 292 metrics.EventsReceivedCounter.WithLabelValues(w.groupResource.String()).Inc() 293 294 key, err := w.keyFunc(event.Object) 295 if err != nil { 296 return fmt.Errorf("couldn't compute key: %v", err) 297 } 298 elem := &storeElement{Key: key, Object: event.Object} 299 elem.Labels, elem.Fields, err = w.getAttrsFunc(event.Object) 300 if err != nil { 301 return err 302 } 303 304 wcEvent := &watchCacheEvent{ 305 Type: event.Type, 306 Object: elem.Object, 307 ObjLabels: elem.Labels, 308 ObjFields: elem.Fields, 309 Key: key, 310 ResourceVersion: resourceVersion, 311 RecordTime: w.clock.Now(), 312 } 313 314 if err := func() error { 315 // TODO: We should consider moving this lock below after the watchCacheEvent 316 // is created. In such situation, the only problematic scenario is Replace() 317 // happening after getting object from store and before acquiring a lock. 318 // Maybe introduce another lock for this purpose. 319 w.Lock() 320 defer w.Unlock() 321 322 previous, exists, err := w.store.Get(elem) 323 if err != nil { 324 return err 325 } 326 if exists { 327 previousElem := previous.(*storeElement) 328 wcEvent.PrevObject = previousElem.Object 329 wcEvent.PrevObjLabels = previousElem.Labels 330 wcEvent.PrevObjFields = previousElem.Fields 331 } 332 333 w.updateCache(wcEvent) 334 w.resourceVersion = resourceVersion 335 defer w.cond.Broadcast() 336 337 return updateFunc(elem) 338 }(); err != nil { 339 return err 340 } 341 342 // Avoid calling event handler under lock. 343 // This is safe as long as there is at most one call to Add/Update/Delete and 344 // UpdateResourceVersion in flight at any point in time, which is true now, 345 // because reflector calls them synchronously from its main thread. 346 if w.eventHandler != nil { 347 w.eventHandler(wcEvent) 348 } 349 return nil 350 } 351 352 // Assumes that lock is already held for write. 353 func (w *watchCache) updateCache(event *watchCacheEvent) { 354 w.resizeCacheLocked(event.RecordTime) 355 if w.isCacheFullLocked() { 356 // Cache is full - remove the oldest element. 357 w.startIndex++ 358 w.removedEventSinceRelist = true 359 } 360 w.cache[w.endIndex%w.capacity] = event 361 w.endIndex++ 362 } 363 364 // resizeCacheLocked resizes the cache if necessary: 365 // - increases capacity by 2x if cache is full and all cached events occurred within last eventFreshDuration. 366 // - decreases capacity by 2x when recent quarter of events occurred outside of eventFreshDuration(protect watchCache from flapping). 367 func (w *watchCache) resizeCacheLocked(eventTime time.Time) { 368 if w.isCacheFullLocked() && eventTime.Sub(w.cache[w.startIndex%w.capacity].RecordTime) < eventFreshDuration { 369 capacity := min(w.capacity*2, w.upperBoundCapacity) 370 if capacity > w.capacity { 371 w.doCacheResizeLocked(capacity) 372 } 373 return 374 } 375 if w.isCacheFullLocked() && eventTime.Sub(w.cache[(w.endIndex-w.capacity/4)%w.capacity].RecordTime) > eventFreshDuration { 376 capacity := max(w.capacity/2, w.lowerBoundCapacity) 377 if capacity < w.capacity { 378 w.doCacheResizeLocked(capacity) 379 } 380 return 381 } 382 } 383 384 // isCacheFullLocked used to judge whether watchCacheEvent is full. 385 // Assumes that lock is already held for write. 386 func (w *watchCache) isCacheFullLocked() bool { 387 return w.endIndex == w.startIndex+w.capacity 388 } 389 390 // doCacheResizeLocked resize watchCache's event array with different capacity. 391 // Assumes that lock is already held for write. 392 func (w *watchCache) doCacheResizeLocked(capacity int) { 393 newCache := make([]*watchCacheEvent, capacity) 394 if capacity < w.capacity { 395 // adjust startIndex if cache capacity shrink. 396 w.startIndex = w.endIndex - capacity 397 } 398 for i := w.startIndex; i < w.endIndex; i++ { 399 newCache[i%capacity] = w.cache[i%w.capacity] 400 } 401 w.cache = newCache 402 metrics.RecordsWatchCacheCapacityChange(w.groupResource.String(), w.capacity, capacity) 403 w.capacity = capacity 404 } 405 406 func (w *watchCache) UpdateResourceVersion(resourceVersion string) { 407 rv, err := w.versioner.ParseResourceVersion(resourceVersion) 408 if err != nil { 409 klog.Errorf("Couldn't parse resourceVersion: %v", err) 410 return 411 } 412 413 func() { 414 w.Lock() 415 defer w.Unlock() 416 w.resourceVersion = rv 417 w.cond.Broadcast() 418 }() 419 420 // Avoid calling event handler under lock. 421 // This is safe as long as there is at most one call to Add/Update/Delete and 422 // UpdateResourceVersion in flight at any point in time, which is true now, 423 // because reflector calls them synchronously from its main thread. 424 if w.eventHandler != nil { 425 wcEvent := &watchCacheEvent{ 426 Type: watch.Bookmark, 427 ResourceVersion: rv, 428 } 429 w.eventHandler(wcEvent) 430 } 431 } 432 433 // List returns list of pointers to <storeElement> objects. 434 func (w *watchCache) List() []interface{} { 435 return w.store.List() 436 } 437 438 // waitUntilFreshAndBlock waits until cache is at least as fresh as given <resourceVersion>. 439 // NOTE: This function acquired lock and doesn't release it. 440 // You HAVE TO explicitly call w.RUnlock() after this function. 441 func (w *watchCache) waitUntilFreshAndBlock(ctx context.Context, resourceVersion uint64) error { 442 startTime := w.clock.Now() 443 444 // In case resourceVersion is 0, we accept arbitrarily stale result. 445 // As a result, the condition in the below for loop will never be 446 // satisfied (w.resourceVersion is never negative), this call will 447 // never hit the w.cond.Wait(). 448 // As a result - we can optimize the code by not firing the wakeup 449 // function (and avoid starting a gorotuine), especially given that 450 // resourceVersion=0 is the most common case. 451 if resourceVersion > 0 { 452 go func() { 453 // Wake us up when the time limit has expired. The docs 454 // promise that time.After (well, NewTimer, which it calls) 455 // will wait *at least* the duration given. Since this go 456 // routine starts sometime after we record the start time, and 457 // it will wake up the loop below sometime after the broadcast, 458 // we don't need to worry about waking it up before the time 459 // has expired accidentally. 460 <-w.clock.After(blockTimeout) 461 w.cond.Broadcast() 462 }() 463 } 464 465 w.RLock() 466 span := tracing.SpanFromContext(ctx) 467 span.AddEvent("watchCache locked acquired") 468 for w.resourceVersion < resourceVersion { 469 if w.clock.Since(startTime) >= blockTimeout { 470 // Request that the client retry after 'resourceVersionTooHighRetrySeconds' seconds. 471 return storage.NewTooLargeResourceVersionError(resourceVersion, w.resourceVersion, resourceVersionTooHighRetrySeconds) 472 } 473 w.cond.Wait() 474 } 475 span.AddEvent("watchCache fresh enough") 476 return nil 477 } 478 479 type sortableStoreElements []interface{} 480 481 func (s sortableStoreElements) Len() int { 482 return len(s) 483 } 484 485 func (s sortableStoreElements) Less(i, j int) bool { 486 return s[i].(*storeElement).Key < s[j].(*storeElement).Key 487 } 488 489 func (s sortableStoreElements) Swap(i, j int) { 490 s[i], s[j] = s[j], s[i] 491 } 492 493 // WaitUntilFreshAndList returns list of pointers to `storeElement` objects along 494 // with their ResourceVersion and the name of the index, if any, that was used. 495 func (w *watchCache) WaitUntilFreshAndList(ctx context.Context, resourceVersion uint64, matchValues []storage.MatchValue) ([]interface{}, uint64, string, error) { 496 var err error 497 if utilfeature.DefaultFeatureGate.Enabled(features.ConsistentListFromCache) && w.notFresh(resourceVersion) { 498 w.waitingUntilFresh.Add() 499 err = w.waitUntilFreshAndBlock(ctx, resourceVersion) 500 w.waitingUntilFresh.Remove() 501 } else { 502 err = w.waitUntilFreshAndBlock(ctx, resourceVersion) 503 } 504 defer w.RUnlock() 505 if err != nil { 506 return nil, 0, "", err 507 } 508 509 result, rv, index, err := func() ([]interface{}, uint64, string, error) { 510 // This isn't the place where we do "final filtering" - only some "prefiltering" is happening here. So the only 511 // requirement here is to NOT miss anything that should be returned. We can return as many non-matching items as we 512 // want - they will be filtered out later. The fact that we return less things is only further performance improvement. 513 // TODO: if multiple indexes match, return the one with the fewest items, so as to do as much filtering as possible. 514 for _, matchValue := range matchValues { 515 if result, err := w.store.ByIndex(matchValue.IndexName, matchValue.Value); err == nil { 516 return result, w.resourceVersion, matchValue.IndexName, nil 517 } 518 } 519 return w.store.List(), w.resourceVersion, "", nil 520 }() 521 522 sort.Sort(sortableStoreElements(result)) 523 return result, rv, index, err 524 } 525 526 func (w *watchCache) notFresh(resourceVersion uint64) bool { 527 w.RLock() 528 defer w.RUnlock() 529 return resourceVersion > w.resourceVersion 530 } 531 532 // WaitUntilFreshAndGet returns a pointers to <storeElement> object. 533 func (w *watchCache) WaitUntilFreshAndGet(ctx context.Context, resourceVersion uint64, key string) (interface{}, bool, uint64, error) { 534 err := w.waitUntilFreshAndBlock(ctx, resourceVersion) 535 defer w.RUnlock() 536 if err != nil { 537 return nil, false, 0, err 538 } 539 value, exists, err := w.store.GetByKey(key) 540 return value, exists, w.resourceVersion, err 541 } 542 543 func (w *watchCache) ListKeys() []string { 544 return w.store.ListKeys() 545 } 546 547 // Get takes runtime.Object as a parameter. However, it returns 548 // pointer to <storeElement>. 549 func (w *watchCache) Get(obj interface{}) (interface{}, bool, error) { 550 object, ok := obj.(runtime.Object) 551 if !ok { 552 return nil, false, fmt.Errorf("obj does not implement runtime.Object interface: %v", obj) 553 } 554 key, err := w.keyFunc(object) 555 if err != nil { 556 return nil, false, fmt.Errorf("couldn't compute key: %v", err) 557 } 558 559 return w.store.Get(&storeElement{Key: key, Object: object}) 560 } 561 562 // GetByKey returns pointer to <storeElement>. 563 func (w *watchCache) GetByKey(key string) (interface{}, bool, error) { 564 return w.store.GetByKey(key) 565 } 566 567 // Replace takes slice of runtime.Object as a parameter. 568 func (w *watchCache) Replace(objs []interface{}, resourceVersion string) error { 569 version, err := w.versioner.ParseResourceVersion(resourceVersion) 570 if err != nil { 571 return err 572 } 573 574 toReplace := make([]interface{}, 0, len(objs)) 575 for _, obj := range objs { 576 object, ok := obj.(runtime.Object) 577 if !ok { 578 return fmt.Errorf("didn't get runtime.Object for replace: %#v", obj) 579 } 580 key, err := w.keyFunc(object) 581 if err != nil { 582 return fmt.Errorf("couldn't compute key: %v", err) 583 } 584 objLabels, objFields, err := w.getAttrsFunc(object) 585 if err != nil { 586 return err 587 } 588 toReplace = append(toReplace, &storeElement{ 589 Key: key, 590 Object: object, 591 Labels: objLabels, 592 Fields: objFields, 593 }) 594 } 595 596 w.Lock() 597 defer w.Unlock() 598 599 // Ensure startIndex never decreases, so that existing watchCacheInterval 600 // instances get "invalid" errors if the try to download from the buffer 601 // using their own start/end indexes calculated from previous buffer 602 // content. 603 604 // Empty the cyclic buffer, ensuring startIndex doesn't decrease. 605 w.startIndex = w.endIndex 606 w.removedEventSinceRelist = false 607 608 if err := w.store.Replace(toReplace, resourceVersion); err != nil { 609 return err 610 } 611 w.listResourceVersion = version 612 w.resourceVersion = version 613 if w.onReplace != nil { 614 w.onReplace() 615 } 616 w.cond.Broadcast() 617 klog.V(3).Infof("Replace watchCache (rev: %v) ", resourceVersion) 618 return nil 619 } 620 621 func (w *watchCache) SetOnReplace(onReplace func()) { 622 w.Lock() 623 defer w.Unlock() 624 w.onReplace = onReplace 625 } 626 627 func (w *watchCache) Resync() error { 628 // Nothing to do 629 return nil 630 } 631 632 func (w *watchCache) currentCapacity() int { 633 w.RLock() 634 defer w.RUnlock() 635 return w.capacity 636 } 637 638 const ( 639 // minWatchChanSize is the min size of channels used by the watch. 640 // We keep that set to 10 for "backward compatibility" until we 641 // convince ourselves based on some metrics that decreasing is safe. 642 minWatchChanSize = 10 643 // maxWatchChanSizeWithIndexAndTriger is the max size of the channel 644 // used by the watch using the index and trigger selector. 645 maxWatchChanSizeWithIndexAndTrigger = 10 646 // maxWatchChanSizeWithIndexWithoutTrigger is the max size of the channel 647 // used by the watch using the index but without triggering selector. 648 // We keep that set to 1000 for "backward compatibility", until we 649 // convinced ourselves based on some metrics that decreasing is safe. 650 maxWatchChanSizeWithIndexWithoutTrigger = 1000 651 // maxWatchChanSizeWithoutIndex is the max size of the channel 652 // used by the watch not using the index. 653 // TODO(wojtek-t): Figure out if the value shouldn't be higher. 654 maxWatchChanSizeWithoutIndex = 100 655 ) 656 657 func (w *watchCache) suggestedWatchChannelSize(indexExists, triggerUsed bool) int { 658 // To estimate the channel size we use a heuristic that a channel 659 // should roughly be able to keep one second of history. 660 // We don't have an exact data, but given we store updates from 661 // the last <eventFreshDuration>, we approach it by dividing the 662 // capacity by the length of the history window. 663 chanSize := int(math.Ceil(float64(w.currentCapacity()) / eventFreshDuration.Seconds())) 664 665 // Finally we adjust the size to avoid ending with too low or 666 // to large values. 667 if chanSize < minWatchChanSize { 668 chanSize = minWatchChanSize 669 } 670 var maxChanSize int 671 switch { 672 case indexExists && triggerUsed: 673 maxChanSize = maxWatchChanSizeWithIndexAndTrigger 674 case indexExists && !triggerUsed: 675 maxChanSize = maxWatchChanSizeWithIndexWithoutTrigger 676 case !indexExists: 677 maxChanSize = maxWatchChanSizeWithoutIndex 678 } 679 if chanSize > maxChanSize { 680 chanSize = maxChanSize 681 } 682 return chanSize 683 } 684 685 // isIndexValidLocked checks if a given index is still valid. 686 // This assumes that the lock is held. 687 func (w *watchCache) isIndexValidLocked(index int) bool { 688 return index >= w.startIndex 689 } 690 691 // getAllEventsSinceLocked returns a watchCacheInterval that can be used to 692 // retrieve events since a certain resourceVersion. This function assumes to 693 // be called under the watchCache lock. 694 func (w *watchCache) getAllEventsSinceLocked(resourceVersion uint64) (*watchCacheInterval, error) { 695 size := w.endIndex - w.startIndex 696 var oldest uint64 697 switch { 698 case w.listResourceVersion > 0 && !w.removedEventSinceRelist: 699 // If no event was removed from the buffer since last relist, the oldest watch 700 // event we can deliver is one greater than the resource version of the list. 701 oldest = w.listResourceVersion + 1 702 case size > 0: 703 // If the previous condition is not satisfied: either some event was already 704 // removed from the buffer or we've never completed a list (the latter can 705 // only happen in unit tests that populate the buffer without performing 706 // list/replace operations), the oldest watch event we can deliver is the first 707 // one in the buffer. 708 oldest = w.cache[w.startIndex%w.capacity].ResourceVersion 709 default: 710 return nil, fmt.Errorf("watch cache isn't correctly initialized") 711 } 712 713 if resourceVersion == 0 { 714 // resourceVersion = 0 means that we don't require any specific starting point 715 // and we would like to start watching from ~now. 716 // However, to keep backward compatibility, we additionally need to return the 717 // current state and only then start watching from that point. 718 // 719 // TODO: In v2 api, we should stop returning the current state - #13969. 720 return w.getIntervalFromStoreLocked() 721 } 722 if resourceVersion < oldest-1 { 723 return nil, errors.NewResourceExpired(fmt.Sprintf("too old resource version: %d (%d)", resourceVersion, oldest-1)) 724 } 725 726 // Binary search the smallest index at which resourceVersion is greater than the given one. 727 f := func(i int) bool { 728 return w.cache[(w.startIndex+i)%w.capacity].ResourceVersion > resourceVersion 729 } 730 first := sort.Search(size, f) 731 indexerFunc := func(i int) *watchCacheEvent { 732 return w.cache[i%w.capacity] 733 } 734 ci := newCacheInterval(w.startIndex+first, w.endIndex, indexerFunc, w.indexValidator, &w.RWMutex) 735 return ci, nil 736 } 737 738 // getIntervalFromStoreLocked returns a watchCacheInterval 739 // that covers the entire storage state. 740 // This function assumes to be called under the watchCache lock. 741 func (w *watchCache) getIntervalFromStoreLocked() (*watchCacheInterval, error) { 742 ci, err := newCacheIntervalFromStore(w.resourceVersion, w.store, w.getAttrsFunc) 743 if err != nil { 744 return nil, err 745 } 746 return ci, nil 747 }