github.com/elfadel/cilium@v1.6.12/pkg/backoff/backoff.go (about)

     1  // Copyright 2018-2019 Authors of Cilium
     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 backoff
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"math"
    21  	"math/rand"
    22  	"time"
    23  
    24  	"github.com/cilium/cilium/pkg/logging"
    25  	"github.com/cilium/cilium/pkg/logging/logfields"
    26  	"github.com/cilium/cilium/pkg/uuid"
    27  
    28  	"github.com/sirupsen/logrus"
    29  )
    30  
    31  var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "backoff")
    32  
    33  // NodeManager is the interface required to implement cluster size dependent
    34  // intervals
    35  type NodeManager interface {
    36  	ClusterSizeDependantInterval(baseInterval time.Duration) time.Duration
    37  }
    38  
    39  // Exponential implements an exponential backoff
    40  type Exponential struct {
    41  	// Min is the minimal backoff time, if unspecified, 1 second will be
    42  	// used
    43  	Min time.Duration
    44  
    45  	// Max is the maximum backoff time, if unspecified, no maximum time is
    46  	// applied
    47  	Max time.Duration
    48  
    49  	// Factor is the factor the backoff time grows exponentially, if
    50  	// unspecified, a factor of 2.0 will be used
    51  	Factor float64
    52  
    53  	// Jitter, when enabled, adds random jitter to the interval
    54  	Jitter bool
    55  
    56  	// NodeManager enables the use of cluster size dependent backoff
    57  	// intervals, i.e. the larger the cluster, the longer the backoff
    58  	// interval
    59  	NodeManager NodeManager
    60  
    61  	// Name is a free form string describing the operation subject to the
    62  	// backoff, if unspecified, a UUID is generated. This string is used
    63  	// for logging purposes.
    64  	Name string
    65  
    66  	attempt int
    67  }
    68  
    69  // CalculateDuration calculates the backoff duration based on minimum base
    70  // interval, exponential factor, jitter and number of failures.
    71  func CalculateDuration(min, max time.Duration, factor float64, jitter bool, failures int) time.Duration {
    72  	minFloat := float64(min)
    73  	maxFloat := float64(max)
    74  
    75  	t := minFloat * math.Pow(factor, float64(failures))
    76  	if max != time.Duration(0) && t > maxFloat {
    77  		t = maxFloat
    78  	}
    79  
    80  	if jitter {
    81  		t = rand.Float64()*(t-minFloat) + minFloat
    82  	}
    83  
    84  	return time.Duration(t)
    85  }
    86  
    87  // Wait waits for the required time using an exponential backoff
    88  func (b *Exponential) Wait(ctx context.Context) error {
    89  	b.attempt++
    90  	t := b.Duration(b.attempt)
    91  
    92  	log.WithFields(logrus.Fields{
    93  		"time":    t,
    94  		"attempt": b.attempt,
    95  		"name":    b.Name,
    96  	}).Debug("Sleeping with exponential backoff")
    97  
    98  	select {
    99  	case <-ctx.Done():
   100  		return fmt.Errorf("exponential backoff cancelled via context: %s", ctx.Err())
   101  	case <-time.After(t):
   102  	}
   103  
   104  	return nil
   105  }
   106  
   107  // Duration returns the wait duration for the nth attempt
   108  func (b *Exponential) Duration(attempt int) time.Duration {
   109  	if b.Name == "" {
   110  		b.Name = uuid.NewUUID().String()
   111  	}
   112  
   113  	min := time.Duration(1) * time.Second
   114  	if b.Min != time.Duration(0) {
   115  		min = b.Min
   116  	}
   117  
   118  	factor := float64(2)
   119  	if b.Factor != float64(0) {
   120  		factor = b.Factor
   121  	}
   122  
   123  	t := CalculateDuration(min, b.Max, factor, b.Jitter, attempt)
   124  
   125  	if b.NodeManager != nil {
   126  		t = b.NodeManager.ClusterSizeDependantInterval(t)
   127  	}
   128  
   129  	if b.Max != time.Duration(0) && t > b.Max {
   130  		t = b.Max
   131  	}
   132  
   133  	return t
   134  }