package resty
import (
"context"
"io"
"math"
"math/rand"
"sync"
"time"
)
const (
defaultMaxRetries = 3
defaultWaitTime = time .Duration (100 ) * time .Millisecond
defaultMaxWaitTime = time .Duration (2000 ) * time .Millisecond
)
type (
Option func (*Options )
RetryConditionFunc func (*Response , error ) bool
OnRetryFunc func (*Response , error )
RetryAfterFunc func (*Client , *Response ) (time .Duration , error )
Options struct {
maxRetries int
waitTime time .Duration
maxWaitTime time .Duration
retryConditions []RetryConditionFunc
retryHooks []OnRetryFunc
resetReaders bool
}
)
func Retries (value int ) Option {
return func (o *Options ) {
o .maxRetries = value
}
}
func WaitTime (value time .Duration ) Option {
return func (o *Options ) {
o .waitTime = value
}
}
func MaxWaitTime (value time .Duration ) Option {
return func (o *Options ) {
o .maxWaitTime = value
}
}
func RetryConditions (conditions []RetryConditionFunc ) Option {
return func (o *Options ) {
o .retryConditions = conditions
}
}
func RetryHooks (hooks []OnRetryFunc ) Option {
return func (o *Options ) {
o .retryHooks = hooks
}
}
func ResetMultipartReaders (value bool ) Option {
return func (o *Options ) {
o .resetReaders = value
}
}
func Backoff (operation func () (*Response , error ), options ...Option ) error {
opts := Options {
maxRetries : defaultMaxRetries ,
waitTime : defaultWaitTime ,
maxWaitTime : defaultMaxWaitTime ,
retryConditions : []RetryConditionFunc {},
}
for _ , o := range options {
o (&opts )
}
var (
resp *Response
err error
)
for attempt := 0 ; attempt <= opts .maxRetries ; attempt ++ {
resp , err = operation ()
ctx := context .Background ()
if resp != nil && resp .Request .ctx != nil {
ctx = resp .Request .ctx
}
if ctx .Err () != nil {
return err
}
err1 := unwrapNoRetryErr (err )
needsRetry := err != nil && err == err1
for _ , condition := range opts .retryConditions {
needsRetry = condition (resp , err1 )
if needsRetry {
break
}
}
if !needsRetry {
return err
}
if opts .resetReaders {
if err := resetFileReaders (resp .Request .multipartFiles ); err != nil {
return err
}
}
for _ , hook := range opts .retryHooks {
hook (resp , err )
}
if attempt == opts .maxRetries {
return err
}
waitTime , err2 := sleepDuration (resp , opts .waitTime , opts .maxWaitTime , attempt )
if err2 != nil {
if err == nil {
err = err2
}
return err
}
select {
case <- time .After (waitTime ):
case <- ctx .Done ():
return ctx .Err ()
}
}
return err
}
func sleepDuration(resp *Response , min , max time .Duration , attempt int ) (time .Duration , error ) {
const maxInt = 1 <<31 - 1
if max < 0 {
max = maxInt
}
if resp == nil {
return jitterBackoff (min , max , attempt ), nil
}
retryAfterFunc := resp .Request .client .RetryAfter
if retryAfterFunc == nil {
return jitterBackoff (min , max , attempt ), nil
}
result , err := retryAfterFunc (resp .Request .client , resp )
if err != nil {
return 0 , err
}
if result == 0 {
return jitterBackoff (min , max , attempt ), nil
}
if result < 0 || max < result {
result = max
}
if result < min {
result = min
}
return result , nil
}
func jitterBackoff(min , max time .Duration , attempt int ) time .Duration {
base := float64 (min )
capLevel := float64 (max )
temp := math .Min (capLevel , base *math .Exp2 (float64 (attempt )))
ri := time .Duration (temp / 2 )
if ri == 0 {
ri = time .Nanosecond
}
result := randDuration (ri )
if result < min {
result = min
}
return result
}
var rnd = newRnd ()
var rndMu sync .Mutex
func randDuration(center time .Duration ) time .Duration {
rndMu .Lock ()
defer rndMu .Unlock ()
var ri = int64 (center )
var jitter = rnd .Int63n (ri )
return time .Duration (math .Abs (float64 (ri + jitter )))
}
func newRnd() *rand .Rand {
var seed = time .Now ().UnixNano ()
var src = rand .NewSource (seed )
return rand .New (src )
}
func resetFileReaders(files []*File ) error {
for _ , f := range files {
if rs , ok := f .Reader .(io .ReadSeeker ); ok {
if _ , err := rs .Seek (0 , io .SeekStart ); err != nil {
return err
}
}
}
return nil
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .