Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support random jitter combined with other delay options. #26

Merged
merged 1 commit into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,17 @@ func IsRecoverable(err error) bool
```
IsRecoverable checks if error is an instance of `unrecoverableError`

#### func RandomDelay

```go
func RandomDelay(_ uint, config *Config) time.Duration
```
RandomDelay is a DelayType which picks a random delay up to config.maxJitter

#### func Unrecoverable

```go
func Unrecoverable(err error) unrecoverableError
func Unrecoverable(err error) error
```
Unrecoverable wraps an error in `unrecoverableError` struct

Expand All @@ -131,6 +138,14 @@ type DelayTypeFunc func(n uint, config *Config) time.Duration
```


#### func CombineDelay

```go
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc
```
CombineDelay is a DelayType the combines all of the specified delays into a new
DelayTypeFunc

#### type Error

```go
Expand Down Expand Up @@ -202,6 +217,13 @@ func LastErrorOnly(lastErrorOnly bool) Option
return the direct last error that came from the retried function default is
false (return wrapped errors with everything)

#### func MaxJitter

```go
func MaxJitter(maxJitter time.Duration) Option
```
MaxJitter sets the maximum random Jitter between retries for RandomDelay

#### func OnRetry

```go
Expand Down Expand Up @@ -242,7 +264,7 @@ skip retry if special error example:
})
)

The default RetryIf stops execution if the error is wrapped using
By default RetryIf stops execution if the error is wrapped using
`retry.Unrecoverable`, so above example may also be shortened to:

retry.Do(
Expand Down
25 changes: 25 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package retry

import (
"math/rand"
"time"
)

Expand All @@ -16,6 +17,7 @@ type DelayTypeFunc func(n uint, config *Config) time.Duration
type Config struct {
attempts uint
delay time.Duration
maxJitter time.Duration
onRetry OnRetryFunc
retryIf RetryIfFunc
delayType DelayTypeFunc
Expand Down Expand Up @@ -49,6 +51,13 @@ func Delay(delay time.Duration) Option {
}
}

// MaxJitter sets the maximum random Jitter between retries for RandomDelay
func MaxJitter(maxJitter time.Duration) Option {
return func(c *Config) {
c.maxJitter = maxJitter
}
}

// DelayType set type of the delay between retries
// default is BackOff
func DelayType(delayType DelayTypeFunc) Option {
Expand All @@ -67,6 +76,22 @@ func FixedDelay(_ uint, config *Config) time.Duration {
return config.delay
}

// RandomDelay is a DelayType which picks a random delay up to config.maxJitter
func RandomDelay(_ uint, config *Config) time.Duration {
return time.Duration(rand.Int63n(int64(config.maxJitter)))
}

// CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
return func(n uint, config *Config) time.Duration {
var total time.Duration
for _, delay := range delays {
total += delay(n, config)
}
return total
}
}

// OnRetry function callback are called each retry
//
// log each retry example:
Expand Down
3 changes: 2 additions & 1 deletion retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
config := &Config{
attempts: 10,
delay: 100 * time.Millisecond,
maxJitter: 100 * time.Millisecond,
onRetry: func(n uint, err error) {},
retryIf: IsRecoverable,
delayType: BackOffDelay,
delayType: CombineDelay(BackOffDelay, RandomDelay),
lastErrorOnly: false,
}

Expand Down
27 changes: 27 additions & 0 deletions retry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,30 @@ func TestUnrecoverableError(t *testing.T) {
assert.Equal(t, expectedErr, err)
assert.Equal(t, 1, attempts, "unrecoverable error broke the loop")
}

func TestCombineFixedDelays(t *testing.T) {
start := time.Now()
err := Do(
func() error { return errors.New("test") },
Attempts(3),
DelayType(CombineDelay(FixedDelay, FixedDelay)),
)
dur := time.Since(start)
assert.Error(t, err)
assert.True(t, dur > 400*time.Millisecond, "3 times combined, fixed retry is longer then 400ms")
assert.True(t, dur < 500*time.Millisecond, "3 times combined, fixed retry is shorter then 500ms")
}

func TestRandomDelay(t *testing.T) {
start := time.Now()
err := Do(
func() error { return errors.New("test") },
Attempts(3),
DelayType(RandomDelay),
MaxJitter(50 * time.Millisecond),
)
dur := time.Since(start)
assert.Error(t, err)
assert.True(t, dur > 2*time.Millisecond, "3 times random retry is longer then 2ms")
assert.True(t, dur < 100*time.Millisecond, "3 times random retry is shorter then 100ms")
}