github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/resty/retry.go (about) 1 // Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. 2 // resty source code and usage is governed by a MIT style 3 // license that can be found in the LICENSE file. 4 5 package resty 6 7 import ( 8 "context" 9 "math" 10 "math/rand" 11 "sync" 12 "time" 13 ) 14 15 const ( 16 defaultMaxRetries = 3 17 defaultWaitTime = time.Duration(100) * time.Millisecond 18 defaultMaxWaitTime = time.Duration(2000) * time.Millisecond 19 ) 20 21 type ( 22 // Option is to create convenient retry options like wait time, max retries, etc. 23 Option func(*Options) 24 25 // RetryConditionFunc type is for retry condition function 26 // input: non-nil Response OR request execution error 27 RetryConditionFunc func(*Response, error) bool 28 29 // OnRetryFunc is for side-effecting functions triggered on retry 30 OnRetryFunc func(*Response, error) 31 32 // RetryAfterFunc returns time to wait before retry 33 // For example, it can parse HTTP Retry-After header 34 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 35 // Non-nil error is returned if it is found that request is not retryable 36 // (0, nil) is a special result means 'use default algorithm' 37 RetryAfterFunc func(*Client, *Response) (time.Duration, error) 38 39 // Options struct is used to hold retry settings. 40 Options struct { 41 maxRetries int 42 waitTime time.Duration 43 maxWaitTime time.Duration 44 retryConditions []RetryConditionFunc 45 retryHooks []OnRetryFunc 46 } 47 ) 48 49 // Retries sets the max number of retries 50 func Retries(v int) Option { return func(o *Options) { o.maxRetries = v } } 51 52 // WaitTime sets the default wait time to sleep between requests 53 func WaitTime(v time.Duration) Option { return func(o *Options) { o.waitTime = v } } 54 55 // MaxWaitTime sets the max wait time to sleep between requests 56 func MaxWaitTime(v time.Duration) Option { return func(o *Options) { o.maxWaitTime = v } } 57 58 // RetryConditions sets the conditions that will be checked for retry. 59 func RetryConditions(conditions []RetryConditionFunc) Option { 60 return func(o *Options) { 61 o.retryConditions = conditions 62 } 63 } 64 65 // RetryHooks sets the hooks that will be executed after each retry 66 func RetryHooks(hooks []OnRetryFunc) Option { 67 return func(o *Options) { 68 o.retryHooks = hooks 69 } 70 } 71 72 // Backoff retries with increasing timeout duration up until X amount of retries 73 // (Default is 3 attempts, Override with option Retries(n)) 74 func Backoff(operation func() (*Response, error), options ...Option) error { 75 // Defaults 76 opts := Options{ 77 maxRetries: defaultMaxRetries, 78 waitTime: defaultWaitTime, 79 maxWaitTime: defaultMaxWaitTime, 80 retryConditions: []RetryConditionFunc{}, 81 } 82 83 for _, o := range options { 84 o(&opts) 85 } 86 87 var resp *Response 88 var err error 89 90 for attempt := 0; attempt <= opts.maxRetries; attempt++ { 91 resp, err = operation() 92 ctx := context.Background() 93 if resp != nil && resp.Request.ctx != nil { 94 ctx = resp.Request.ctx 95 } 96 if ctx.Err() != nil { 97 return err 98 } 99 100 err1 := unwrapNoRetryErr(err) // raw error, it used for return users callback. 101 needsRetry := err != nil && err == err1 // retry on a few operation errors by default 102 103 for _, condition := range opts.retryConditions { 104 needsRetry = condition(resp, err1) 105 if needsRetry { 106 break 107 } 108 } 109 110 if !needsRetry { 111 return err 112 } 113 114 for _, hook := range opts.retryHooks { 115 hook(resp, err) 116 } 117 118 // Don't need to wait when no retries left. 119 // Still run retry hooks even on last retry to keep compatibility. 120 if attempt == opts.maxRetries { 121 return err 122 } 123 124 waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt) 125 if err2 != nil { 126 if err == nil { 127 err = err2 128 } 129 return err 130 } 131 132 select { 133 case <-time.After(waitTime): 134 case <-ctx.Done(): 135 return ctx.Err() 136 } 137 } 138 139 return err 140 } 141 142 func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) { 143 const maxInt = 1<<31 - 1 // max int for arch 386 144 if max < 0 { 145 max = maxInt 146 } 147 if resp == nil { 148 return jitterBackoff(min, max, attempt), nil 149 } 150 151 retryAfterFunc := resp.Request.client.RetryAfter 152 153 // Check for custom callback 154 if retryAfterFunc == nil { 155 return jitterBackoff(min, max, attempt), nil 156 } 157 158 result, err := retryAfterFunc(resp.Request.client, resp) 159 if err != nil { 160 return 0, err // i.e. 'API quota exceeded' 161 } 162 if result == 0 { 163 return jitterBackoff(min, max, attempt), nil 164 } 165 if result < 0 || max < result { 166 result = max 167 } 168 if result < min { 169 result = min 170 } 171 return result, nil 172 } 173 174 // Return capped exponential backoff with jitter 175 // http://www.awsarchitectureblog.com/2015/03/backoff.html 176 func jitterBackoff(min, max time.Duration, attempt int) time.Duration { 177 base := float64(min) 178 capLevel := float64(max) 179 180 temp := math.Min(capLevel, base*math.Exp2(float64(attempt))) 181 ri := time.Duration(temp / 2) 182 result := randDuration(ri) 183 184 if result < min { 185 result = min 186 } 187 188 return result 189 } 190 191 var ( 192 rnd = newRnd() 193 rndMu sync.Mutex 194 ) 195 196 func randDuration(center time.Duration) time.Duration { 197 rndMu.Lock() 198 defer rndMu.Unlock() 199 200 ri := int64(center) 201 jitter := rnd.Int63n(ri) 202 return time.Duration(math.Abs(float64(ri + jitter))) 203 } 204 205 func newRnd() *rand.Rand { 206 seed := time.Now().UnixNano() 207 src := rand.NewSource(seed) 208 return rand.New(src) 209 }