Skip to content

Commit

Permalink
Merge pull request #26 from acourtneybrown/master
Browse files Browse the repository at this point in the history
Support random jitter combined with other delay options.
  • Loading branch information
JaSei authored Jan 28, 2020
2 parents f9a5f37 + b05e677 commit 88a508d
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 3 deletions.
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")
}

0 comments on commit 88a508d

Please sign in to comment.