1// Copyright (c) 2015-2019 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 5package resty 6 7import ( 8 "math" 9 "math/rand" 10 "time" 11) 12 13const ( 14 defaultMaxRetries = 3 15 defaultWaitTime = time.Duration(100) * time.Millisecond 16 defaultMaxWaitTime = time.Duration(2000) * time.Millisecond 17) 18 19type ( 20 // Option is to create convenient retry options like wait time, max retries, etc. 21 Option func(*Options) 22 23 // RetryConditionFunc type is for retry condition function 24 RetryConditionFunc func(*Response) (bool, error) 25 26 // Options to hold go-resty retry values 27 Options struct { 28 maxRetries int 29 waitTime time.Duration 30 maxWaitTime time.Duration 31 retryConditions []RetryConditionFunc 32 } 33) 34 35// Retries sets the max number of retries 36func Retries(value int) Option { 37 return func(o *Options) { 38 o.maxRetries = value 39 } 40} 41 42// WaitTime sets the default wait time to sleep between requests 43func WaitTime(value time.Duration) Option { 44 return func(o *Options) { 45 o.waitTime = value 46 } 47} 48 49// MaxWaitTime sets the max wait time to sleep between requests 50func MaxWaitTime(value time.Duration) Option { 51 return func(o *Options) { 52 o.maxWaitTime = value 53 } 54} 55 56// RetryConditions sets the conditions that will be checked for retry. 57func RetryConditions(conditions []RetryConditionFunc) Option { 58 return func(o *Options) { 59 o.retryConditions = conditions 60 } 61} 62 63// Backoff retries with increasing timeout duration up until X amount of retries 64// (Default is 3 attempts, Override with option Retries(n)) 65func Backoff(operation func() (*Response, error), options ...Option) error { 66 // Defaults 67 opts := Options{ 68 maxRetries: defaultMaxRetries, 69 waitTime: defaultWaitTime, 70 maxWaitTime: defaultMaxWaitTime, 71 retryConditions: []RetryConditionFunc{}, 72 } 73 74 for _, o := range options { 75 o(&opts) 76 } 77 78 var ( 79 resp *Response 80 err error 81 ) 82 base := float64(opts.waitTime) // Time to wait between each attempt 83 capLevel := float64(opts.maxWaitTime) // Maximum amount of wait time for the retry 84 for attempt := 0; attempt < opts.maxRetries; attempt++ { 85 resp, err = operation() 86 87 var needsRetry bool 88 var conditionErr error 89 for _, condition := range opts.retryConditions { 90 needsRetry, conditionErr = condition(resp) 91 if needsRetry || conditionErr != nil { 92 break 93 } 94 } 95 96 // If the operation returned no error, there was no condition satisfied and 97 // there was no error caused by the conditional functions. 98 if err == nil && !needsRetry && conditionErr == nil { 99 return nil 100 } 101 // Adding capped exponential backup with jitter 102 // See the following article... 103 // http://www.awsarchitectureblog.com/2015/03/backoff.html 104 temp := math.Min(capLevel, base*math.Exp2(float64(attempt))) 105 ri := int(temp / 2) 106 if ri <= 0 { 107 ri = 1<<31 - 1 // max int for arch 386 108 } 109 sleepDuration := time.Duration(math.Abs(float64(ri + rand.Intn(ri)))) 110 111 if sleepDuration < opts.waitTime { 112 sleepDuration = opts.waitTime 113 } 114 time.Sleep(sleepDuration) 115 } 116 117 return err 118} 119