-
Notifications
You must be signed in to change notification settings - Fork 718
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from keithballdotnet/retry2
Added backoff retry mechanism.
- Loading branch information
Showing
6 changed files
with
164 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package resty | ||
|
||
import ( | ||
"math" | ||
"math/rand" | ||
"time" | ||
) | ||
|
||
type function func() error | ||
|
||
// Option ... | ||
type Option func(*Options) | ||
|
||
// Options ... | ||
type Options struct { | ||
maxRetries int | ||
waitTime int | ||
maxWaitTime int | ||
} | ||
|
||
// Retries sets the max number of retries | ||
func Retries(value int) Option { | ||
return func(o *Options) { | ||
o.maxRetries = value | ||
} | ||
} | ||
|
||
// WaitTime sets the default wait time to sleep between requests | ||
func WaitTime(value int) Option { | ||
return func(o *Options) { | ||
o.waitTime = value | ||
} | ||
} | ||
|
||
// MaxWaitTime sets the max wait time to sleep between requests | ||
func MaxWaitTime(value int) Option { | ||
return func(o *Options) { | ||
o.maxWaitTime = value | ||
} | ||
} | ||
|
||
//Backoff retries with increasing timeout duration up until X amount of retries (Default is 3 attempts, Override with option Retries(n)) | ||
func Backoff(operation function, options ...Option) error { | ||
// Defaults | ||
opts := Options{maxRetries: 3, waitTime: 100, maxWaitTime: 2000} | ||
for _, o := range options { | ||
o(&opts) | ||
} | ||
|
||
var err error | ||
base := float64(opts.waitTime) // Time to wait between each attempt | ||
capLevel := float64(opts.maxWaitTime) // Maximum amount of wait time for the retry | ||
for attempt := 0; attempt < opts.maxRetries; attempt++ { | ||
err = operation() | ||
if err == nil { | ||
return nil | ||
} | ||
// Adding capped exponential backup with jitter | ||
// See the following article... | ||
// http://www.awsarchitectureblog.com/2015/03/backoff.html | ||
temp := math.Min(capLevel, base*math.Exp2(float64(attempt))) | ||
sleepTime := int(temp/2) + rand.Intn(int(temp/2)) | ||
|
||
sleepDuration := time.Duration(sleepTime) * time.Millisecond | ||
time.Sleep(sleepDuration) | ||
} | ||
|
||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package resty | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
) | ||
|
||
func TestBackoffSuccess(t *testing.T) { | ||
attempts := 3 | ||
externalCounter := 0 | ||
retryErr := Backoff(func() error { | ||
externalCounter++ | ||
if externalCounter < attempts { | ||
return errors.New("Not yet got the number we're after...") | ||
} | ||
return nil | ||
}) | ||
|
||
assertError(t, retryErr) | ||
assertEqual(t, externalCounter, attempts) | ||
} | ||
|
||
func TestBackoffTenAttemptsSuccess(t *testing.T) { | ||
attempts := 10 | ||
externalCounter := 0 | ||
retryErr := Backoff(func() error { | ||
externalCounter++ | ||
if externalCounter < attempts { | ||
return errors.New("Not yet got the number we're after...") | ||
} | ||
return nil | ||
}, Retries(attempts), WaitTime(5), MaxWaitTime(500)) | ||
|
||
assertError(t, retryErr) | ||
assertEqual(t, externalCounter, attempts) | ||
} |