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 "time" 29 30 grpcbackoff "google.golang.org/grpc/backoff" 31 "google.golang.org/grpc/internal/grpcrand" 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*(grpcrand.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