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  }