Skip to content

Commit

Permalink
Merge pull request #3 from avast/retry_if
Browse files Browse the repository at this point in the history
retryIf
  • Loading branch information
JaSei authored Nov 24, 2017
2 parents 0556298 + e31ba39 commit 5e47352
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 121 deletions.
111 changes: 72 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ http get with retry:
url := "http://example.com"
var body []byte

err := retry.Retry(
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
Expand Down Expand Up @@ -61,29 +61,23 @@ slightly similar as this package, don't have 'simple' `Retry` method
* [matryer/try](https://github.com/matryer/try) - very popular package,
nonintuitive interface (for me)

## Usage

#### func Retry
### BREAKING CHANGES

```go
func Retry(retryableFunction Retryable) error
```
Retry - simple retry
0.3.0 -> 1.0.0

#### func RetryCustom
* `retry.Retry` function are changed to `retry.Do` function

```go
func RetryCustom(retryableFunction Retryable, onRetryFunction OnRetry, opts RetryOpts) error
```
RetryCustom - the most customizable retry is possible set OnRetry function
callback which are called each retry
* `retry.RetryCustom` (OnRetry) and `retry.RetryCustomWithOpts` functions are
now implement via functions produces Options (aka `retry.OnRetryFunction`)

## Usage

#### func RetryWithOpts
#### func Do

```go
func RetryWithOpts(retryableFunction Retryable, opts RetryOpts) error
func Do(retryableFunc RetryableFunc, opts ...Option) error
```
RetryWithOpts - customizable retry via RetryOpts

#### type Error

Expand Down Expand Up @@ -111,57 +105,96 @@ implementation of the `errwrap.Wrapper` interface in package
[errwrap](https://github.com/hashicorp/errwrap) so that `retry.Error` can be
used with that library.

#### type OnRetry
#### type OnRetryFunc

```go
type OnRetry func(n uint, err error)
type OnRetryFunc func(n uint, err error)
```

Function signature of OnRetry function n = count of tries
Function signature of OnRetry function n = count of attempts

#### type RetryOpts
#### type Option

```go
type RetryOpts struct {
}
type Option func(*config)
```

Struct for configure retry tries - count of tries delay - waiting time units -
waiting time unit (for tests purpose)
Option represents an option for retry.

#### func NewRetryOpts
#### func Attempts

```go
func NewRetryOpts() RetryOpts
func Attempts(attempts uint) Option
```
Create new RetryOpts struct with default values default tries are 10 default
delay are 1e5 default units are microsecond
Attempts set count of retry default is 10

#### func (RetryOpts) Delay
#### func Delay

```go
func (opts RetryOpts) Delay(delay time.Duration) RetryOpts
func Delay(delay time.Duration) Option
```
Delay setter
Delay set delay between retry default are 1e5 units

#### func (RetryOpts) Tries
#### func OnRetry

```go
func (opts RetryOpts) Tries(tries uint) RetryOpts
func OnRetry(onRetry OnRetryFunc) Option
```
Tries setter
OnRetry function callback are called each retry

#### func (RetryOpts) Units
log each retry example:

retry.Do(
func() error {
return errors.New("some error")
},
retry.OnRetry(func(n unit, err error) {
log.Printf("#%d: %s\n", n, err)
}),
)

#### func RetryIf

```go
func (opts RetryOpts) Units(timeUnit time.Duration) RetryOpts
func RetryIf(retryIf RetryIfFunc) Option
```
Units setter
RetryIf controls whether a retry should be attempted after an error (assuming
there are any retry attempts remaining)

skip retry if special error example:

retry.Do(
func() error {
return errors.New("special error")
},
retry.RetryIf(func(err error) bool {
if err.Error() == "special error" {
return false
}
return true
})
)

#### func Units

```go
func Units(units time.Duration) Option
```
Units set unit of delay (probably only for tests purpose) default are
microsecond

#### type RetryIfFunc

```go
type RetryIfFunc func(error) bool
```

Function signature of retry if function

#### type Retryable
#### type RetryableFunc

```go
type Retryable func() error
type RetryableFunc func() error
```

Function signature of retryable function
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.0
1.0.0
2 changes: 1 addition & 1 deletion examples/http_get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestGet(t *testing.T) {
url := "http://example.com"
var body []byte

err := retry.Retry(
err := retry.Do(
func() error {
resp, err := http.Get(url)

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

import (
"time"
)

// Function signature of retry if function
type RetryIfFunc func(error) bool

// Function signature of OnRetry function
// n = count of attempts
type OnRetryFunc func(n uint, err error)

type config struct {
attempts uint
delay time.Duration
units time.Duration
onRetry OnRetryFunc
retryIf RetryIfFunc
}

// Option represents an option for retry.
type Option func(*config)

// Attempts set count of retry
// default is 10
func Attempts(attempts uint) Option {
return func(c *config) {
c.attempts = attempts
}
}

// Delay set delay between retry
// default are 1e5 units
func Delay(delay time.Duration) Option {
return func(c *config) {
c.delay = delay
}
}

// Units set unit of delay (probably only for tests purpose)
// default are microsecond
func Units(units time.Duration) Option {
return func(c *config) {
c.units = units
}
}

// OnRetry function callback are called each retry
//
// log each retry example:
//
// retry.Do(
// func() error {
// return errors.New("some error")
// },
// retry.OnRetry(func(n unit, err error) {
// log.Printf("#%d: %s\n", n, err)
// }),
// )
func OnRetry(onRetry OnRetryFunc) Option {
return func(c *config) {
c.onRetry = onRetry
}
}

// RetryIf controls whether a retry should be attempted after an error
// (assuming there are any retry attempts remaining)
//
// skip retry if special error example:
//
// retry.Do(
// func() error {
// return errors.New("special error")
// },
// retry.RetryIf(func(err error) bool {
// if err.Error() == "special error" {
// return false
// }
// return true
// })
// )
func RetryIf(retryIf RetryIfFunc) Option {
return func(c *config) {
c.retryIf = retryIf
}
}
77 changes: 49 additions & 28 deletions retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ http get with retry:
url := "http://example.com"
var body []byte
err := retry.Retry(
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
Expand Down Expand Up @@ -43,6 +43,15 @@ SEE ALSO
* [matryer/try](https://github.com/matryer/try) - very popular package, nonintuitive interface (for me)
BREAKING CHANGES
0.3.0 -> 1.0.0
* `retry.Retry` function are changed to `retry.Do` function
* `retry.RetryCustom` (OnRetry) and `retry.RetryCustomWithOpts` functions are now implement via functions produces Options (aka `retry.OnRetry`)
*/
package retry

Expand All @@ -53,39 +62,39 @@ import (
)

// Function signature of retryable function
type Retryable func() error
type RetryableFunc func() error

// Function signature of OnRetry function
// n = count of tries
type OnRetry func(n uint, err error)

// Retry - simple retry
func Retry(retryableFunction Retryable) error {
return RetryWithOpts(retryableFunction, NewRetryOpts())
}
func Do(retryableFunc RetryableFunc, opts ...Option) error {
var n uint

// RetryWithOpts - customizable retry via RetryOpts
func RetryWithOpts(retryableFunction Retryable, opts RetryOpts) error {
return RetryCustom(retryableFunction, func(n uint, err error) {}, opts)
}
//default
config := &config{
attempts: 10,
delay: 1e5,
onRetry: func(n uint, err error) {},
retryIf: func(err error) bool { return true },
}

// RetryCustom - the most customizable retry
// is possible set OnRetry function callback
// which are called each retry
func RetryCustom(retryableFunction Retryable, onRetryFunction OnRetry, opts RetryOpts) error {
var n uint
//apply opts
for _, opt := range opts {
opt(config)
}

errorLog := make(Error, opts.tries)
errorLog := make(Error, config.attempts)

for n < opts.tries {
err := retryableFunction()
for n < config.attempts {
err := retryableFunc()

if err != nil {
onRetryFunction(n, err)
config.onRetry(n, err)
errorLog[n] = err

delayTime := opts.delay * (1 << (n - 1))
time.Sleep((time.Duration)(delayTime) * opts.units)
if !config.retryIf(err) {
break
}

delayTime := config.delay * (1 << (n - 1))
time.Sleep((time.Duration)(delayTime) * config.units)
} else {
return nil
}
Expand All @@ -102,12 +111,24 @@ type Error []error
// Error method return string representation of Error
// It is an implementation of error interface
func (e Error) Error() string {
logWithNumber := make([]string, len(e))
logWithNumber := make([]string, lenWithoutNil(e))
for i, l := range e {
logWithNumber[i] = fmt.Sprintf("#%d: %s", i+1, l.Error())
if l != nil {
logWithNumber[i] = fmt.Sprintf("#%d: %s", i+1, l.Error())
}
}

return fmt.Sprintf("All attempts fail:\n%s", strings.Join(logWithNumber, "\n"))
}

func lenWithoutNil(e Error) (count int) {
for _, v := range e {
if v != nil {
count++
}
}

return fmt.Sprintf("All retries fail:\n%s", strings.Join(logWithNumber, "\n"))
return
}

// WrappedErrors returns the list of errors that this Error is wrapping.
Expand Down
Loading

0 comments on commit 5e47352

Please sign in to comment.