1 // Code created by gotmpl. DO NOT MODIFY. 2 // source: internal/shared/otlp/retry/retry.go.tmpl 3 4 // Copyright The OpenTelemetry Authors 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 // Package retry provides request retry functionality that can perform 19 // configurable exponential backoff for transient errors and honor any 20 // explicit throttle responses received. 21 package retry // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry" 22 23 import ( 24 "context" 25 "fmt" 26 "time" 27 28 "github.com/cenkalti/backoff/v4" 29 ) 30 31 // DefaultConfig are the recommended defaults to use. 32 var DefaultConfig = Config{ 33 Enabled: true, 34 InitialInterval: 5 * time.Second, 35 MaxInterval: 30 * time.Second, 36 MaxElapsedTime: time.Minute, 37 } 38 39 // Config defines configuration for retrying batches in case of export failure 40 // using an exponential backoff. 41 type Config struct { 42 // Enabled indicates whether to not retry sending batches in case of 43 // export failure. 44 Enabled bool 45 // InitialInterval the time to wait after the first failure before 46 // retrying. 47 InitialInterval time.Duration 48 // MaxInterval is the upper bound on backoff interval. Once this value is 49 // reached the delay between consecutive retries will always be 50 // `MaxInterval`. 51 MaxInterval time.Duration 52 // MaxElapsedTime is the maximum amount of time (including retries) spent 53 // trying to send a request/batch. Once this value is reached, the data 54 // is discarded. 55 MaxElapsedTime time.Duration 56 } 57 58 // RequestFunc wraps a request with retry logic. 59 type RequestFunc func(context.Context, func(context.Context) error) error 60 61 // EvaluateFunc returns if an error is retry-able and if an explicit throttle 62 // duration should be honored that was included in the error. 63 // 64 // The function must return true if the error argument is retry-able, 65 // otherwise it must return false for the first return parameter. 66 // 67 // The function must return a non-zero time.Duration if the error contains 68 // explicit throttle duration that should be honored, otherwise it must return 69 // a zero valued time.Duration. 70 type EvaluateFunc func(error) (bool, time.Duration) 71 72 // RequestFunc returns a RequestFunc using the evaluate function to determine 73 // if requests can be retried and based on the exponential backoff 74 // configuration of c. 75 func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc { 76 if !c.Enabled { 77 return func(ctx context.Context, fn func(context.Context) error) error { 78 return fn(ctx) 79 } 80 } 81 82 return func(ctx context.Context, fn func(context.Context) error) error { 83 // Do not use NewExponentialBackOff since it calls Reset and the code here 84 // must call Reset after changing the InitialInterval (this saves an 85 // unnecessary call to Now). 86 b := &backoff.ExponentialBackOff{ 87 InitialInterval: c.InitialInterval, 88 RandomizationFactor: backoff.DefaultRandomizationFactor, 89 Multiplier: backoff.DefaultMultiplier, 90 MaxInterval: c.MaxInterval, 91 MaxElapsedTime: c.MaxElapsedTime, 92 Stop: backoff.Stop, 93 Clock: backoff.SystemClock, 94 } 95 b.Reset() 96 97 for { 98 err := fn(ctx) 99 if err == nil { 100 return nil 101 } 102 103 retryable, throttle := evaluate(err) 104 if !retryable { 105 return err 106 } 107 108 bOff := b.NextBackOff() 109 if bOff == backoff.Stop { 110 return fmt.Errorf("max retry time elapsed: %w", err) 111 } 112 113 // Wait for the greater of the backoff or throttle delay. 114 var delay time.Duration 115 if bOff > throttle { 116 delay = bOff 117 } else { 118 elapsed := b.GetElapsedTime() 119 if b.MaxElapsedTime != 0 && elapsed+throttle > b.MaxElapsedTime { 120 return fmt.Errorf("max retry time would elapse: %w", err) 121 } 122 delay = throttle 123 } 124 125 if ctxErr := waitFunc(ctx, delay); ctxErr != nil { 126 return fmt.Errorf("%w: %s", ctxErr, err) 127 } 128 } 129 } 130 } 131 132 // Allow override for testing. 133 var waitFunc = wait 134 135 // wait takes the caller's context, and the amount of time to wait. It will 136 // return nil if the timer fires before or at the same time as the context's 137 // deadline. This indicates that the call can be retried. 138 func wait(ctx context.Context, delay time.Duration) error { 139 timer := time.NewTimer(delay) 140 defer timer.Stop() 141 142 select { 143 case <-ctx.Done(): 144 // Handle the case where the timer and context deadline end 145 // simultaneously by prioritizing the timer expiration nil value 146 // response. 147 select { 148 case <-timer.C: 149 default: 150 return ctx.Err() 151 } 152 case <-timer.C: 153 } 154 155 return nil 156 } 157