istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/util/concurrent/debouncer.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package concurrent 16 17 import ( 18 "time" 19 20 "istio.io/istio/pkg/util/sets" 21 ) 22 23 type Debouncer[T comparable] struct{} 24 25 func (d *Debouncer[T]) Run(ch chan T, stopCh <-chan struct{}, debounceMinInterval, debounceMaxInterval time.Duration, pushFn func(sets.Set[T])) { 26 var timeChan <-chan time.Time 27 var startDebounce time.Time 28 var lastConfigUpdateTime time.Time 29 30 pushCounter := 0 31 debouncedEvents := 0 32 33 // Keeps track of the push requests. If updates are debounce they will be merged. 34 combinedEvents := sets.New[T]() 35 36 free := true 37 freeCh := make(chan struct{}, 1) 38 39 push := func(events sets.Set[T], debouncedEvents int, startDebounce time.Time) { 40 pushFn(events) 41 freeCh <- struct{}{} 42 } 43 44 pushWorker := func() { 45 eventDelay := time.Since(startDebounce) 46 quietTime := time.Since(lastConfigUpdateTime) 47 // it has been too long or quiet enough 48 if eventDelay >= debounceMaxInterval || quietTime >= debounceMinInterval { 49 if combinedEvents.Len() > 0 { 50 pushCounter++ 51 free = false 52 go push(combinedEvents, debouncedEvents, startDebounce) 53 combinedEvents = sets.New[T]() 54 debouncedEvents = 0 55 } else { 56 // For no combined events to process, we can also do nothing here and wait for the config change to trigger 57 // the next debounce, but I think it's better to set it's to the debounce max interval. 58 timeChan = time.After(debounceMaxInterval) 59 } 60 } else { 61 timeChan = time.After(debounceMinInterval - quietTime) 62 } 63 } 64 65 for { 66 select { 67 case <-freeCh: 68 free = true 69 pushWorker() 70 case r := <-ch: 71 72 lastConfigUpdateTime = time.Now() 73 if debouncedEvents == 0 { 74 timeChan = time.After(debounceMinInterval) 75 startDebounce = lastConfigUpdateTime 76 } 77 debouncedEvents++ 78 79 combinedEvents = combinedEvents.Insert(r) 80 case <-timeChan: 81 if free { 82 pushWorker() 83 } 84 case <-stopCh: 85 return 86 } 87 } 88 }