backoff.go raw

   1  /*
   2   *
   3   * Copyright 2017 gRPC authors.
   4   *
   5   * Licensed under the Apache License, Version 2.0 (the "License");
   6   * you may not use this file except in compliance with the License.
   7   * You may obtain a copy of the License at
   8   *
   9   *     http://www.apache.org/licenses/LICENSE-2.0
  10   *
  11   * Unless required by applicable law or agreed to in writing, software
  12   * distributed under the License is distributed on an "AS IS" BASIS,
  13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   * See the License for the specific language governing permissions and
  15   * limitations under the License.
  16   *
  17   */
  18  
  19  // Package backoff implement the backoff strategy for gRPC.
  20  //
  21  // This is kept in internal until the gRPC project decides whether or not to
  22  // allow alternative backoff strategies.
  23  package backoff
  24  
  25  import (
  26  	"context"
  27  	"errors"
  28  	rand "math/rand/v2"
  29  	"time"
  30  
  31  	grpcbackoff "google.golang.org/grpc/backoff"
  32  )
  33  
  34  // Strategy defines the methodology for backing off after a grpc connection
  35  // failure.
  36  type Strategy interface {
  37  	// Backoff returns the amount of time to wait before the next retry given
  38  	// the number of consecutive failures.
  39  	Backoff(retries int) time.Duration
  40  }
  41  
  42  // DefaultExponential is an exponential backoff implementation using the
  43  // default values for all the configurable knobs defined in
  44  // https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
  45  var DefaultExponential = Exponential{Config: grpcbackoff.DefaultConfig}
  46  
  47  // Exponential implements exponential backoff algorithm as defined in
  48  // https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
  49  type Exponential struct {
  50  	// Config contains all options to configure the backoff algorithm.
  51  	Config grpcbackoff.Config
  52  }
  53  
  54  // Backoff returns the amount of time to wait before the next retry given the
  55  // number of retries.
  56  func (bc Exponential) Backoff(retries int) time.Duration {
  57  	if retries == 0 {
  58  		return bc.Config.BaseDelay
  59  	}
  60  	backoff, max := float64(bc.Config.BaseDelay), float64(bc.Config.MaxDelay)
  61  	for backoff < max && retries > 0 {
  62  		backoff *= bc.Config.Multiplier
  63  		retries--
  64  	}
  65  	if backoff > max {
  66  		backoff = max
  67  	}
  68  	// Randomize backoff delays so that if a cluster of requests start at
  69  	// the same time, they won't operate in lockstep.
  70  	backoff *= 1 + bc.Config.Jitter*(rand.Float64()*2-1)
  71  	if backoff < 0 {
  72  		return 0
  73  	}
  74  	return time.Duration(backoff)
  75  }
  76  
  77  // ErrResetBackoff is the error to be returned by the function executed by RunF,
  78  // to instruct the latter to reset its backoff state.
  79  var ErrResetBackoff = errors.New("reset backoff state")
  80  
  81  // RunF provides a convenient way to run a function f repeatedly until the
  82  // context expires or f returns a non-nil error that is not ErrResetBackoff.
  83  // When f returns ErrResetBackoff, RunF continues to run f, but resets its
  84  // backoff state before doing so. backoff accepts an integer representing the
  85  // number of retries, and returns the amount of time to backoff.
  86  func RunF(ctx context.Context, f func() error, backoff func(int) time.Duration) {
  87  	attempt := 0
  88  	timer := time.NewTimer(0)
  89  	for ctx.Err() == nil {
  90  		select {
  91  		case <-timer.C:
  92  		case <-ctx.Done():
  93  			timer.Stop()
  94  			return
  95  		}
  96  
  97  		err := f()
  98  		if errors.Is(err, ErrResetBackoff) {
  99  			timer.Reset(0)
 100  			attempt = 0
 101  			continue
 102  		}
 103  		if err != nil {
 104  			return
 105  		}
 106  		timer.Reset(backoff(attempt))
 107  		attempt++
 108  	}
 109  }
 110