Update github.com/avast/retry-go to v4.5.0

This commit is contained in:
Ciprian Hacman
2023-09-24 11:24:47 +02:00
parent 698c8b067c
commit a5aadf719a
20 changed files with 612 additions and 344 deletions

View File

@@ -1,20 +0,0 @@
language: go
go:
- 1.8
- 1.9
- "1.10"
- 1.11
- 1.12
- 1.13
- 1.14
- 1.15
install:
- make setup
script:
- make ci
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@@ -1,3 +0,0 @@
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.1.4"

View File

@@ -1 +0,0 @@
3.0.0

View File

@@ -1,19 +0,0 @@
version: "{build}"
clone_folder: c:\Users\appveyor\go\src\github.com\avast\retry-go
#os: Windows Server 2012 R2
platform: x64
install:
- copy c:\MinGW\bin\mingw32-make.exe c:\MinGW\bin\make.exe
- set GOPATH=C:\Users\appveyor\go
- set PATH=%PATH%;c:\MinGW\bin
- set PATH=%PATH%;%GOPATH%\bin;c:\go\bin
- set GOBIN=%GOPATH%\bin
- go version
- go env
- make setup
build_script:
- make ci

View File

@@ -1,225 +0,0 @@
/*
Simple library for retry mechanism
slightly inspired by [Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry)
SYNOPSIS
http get with retry:
url := "http://example.com"
var body []byte
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
},
)
fmt.Println(body)
[next examples](https://github.com/avast/retry-go/tree/master/examples)
SEE ALSO
* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly complicated interface.
* [sethgrid/pester](https://github.com/sethgrid/pester) - only http retry for http calls with retries and backoff
* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Go port of the exponential backoff algorithm from Google's HTTP Client Library for Java. Really complicated interface.
* [rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go) - looks good, 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)
BREAKING CHANGES
3.0.0
* `DelayTypeFunc` accepts a new parameter `err` - this breaking change affects only your custom Delay Functions. This change allow [make delay functions based on error](examples/delay_based_on_error_test.go).
1.0.2 -> 2.0.0
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore)
* function `retry.Units` are removed
* [more about this breaking change](https://github.com/avast/retry-go/issues/7)
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
import (
"context"
"fmt"
"strings"
"time"
)
// Function signature of retryable function
type RetryableFunc func() error
var (
DefaultAttempts = uint(10)
DefaultDelay = 100 * time.Millisecond
DefaultMaxJitter = 100 * time.Millisecond
DefaultOnRetry = func(n uint, err error) {}
DefaultRetryIf = IsRecoverable
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
DefaultLastErrorOnly = false
DefaultContext = context.Background()
)
func Do(retryableFunc RetryableFunc, opts ...Option) error {
var n uint
//default
config := &Config{
attempts: DefaultAttempts,
delay: DefaultDelay,
maxJitter: DefaultMaxJitter,
onRetry: DefaultOnRetry,
retryIf: DefaultRetryIf,
delayType: DefaultDelayType,
lastErrorOnly: DefaultLastErrorOnly,
context: DefaultContext,
}
//apply opts
for _, opt := range opts {
opt(config)
}
if err := config.context.Err(); err != nil {
return err
}
var errorLog Error
if !config.lastErrorOnly {
errorLog = make(Error, config.attempts)
} else {
errorLog = make(Error, 1)
}
lastErrIndex := n
for n < config.attempts {
err := retryableFunc()
if err != nil {
errorLog[lastErrIndex] = unpackUnrecoverable(err)
if !config.retryIf(err) {
break
}
config.onRetry(n, err)
// if this is last attempt - don't wait
if n == config.attempts-1 {
break
}
delayTime := config.delayType(n, err, config)
if config.maxDelay > 0 && delayTime > config.maxDelay {
delayTime = config.maxDelay
}
select {
case <-time.After(delayTime):
case <-config.context.Done():
return config.context.Err()
}
} else {
return nil
}
n++
if !config.lastErrorOnly {
lastErrIndex = n
}
}
if config.lastErrorOnly {
return errorLog[lastErrIndex]
}
return errorLog
}
// Error type represents list of errors in retry
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, lenWithoutNil(e))
for i, l := range e {
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
}
// WrappedErrors returns the list of errors that this Error is wrapping.
// It is an implementation of the `errwrap.Wrapper` interface
// in package [errwrap](https://github.com/hashicorp/errwrap) so that
// `retry.Error` can be used with that library.
func (e Error) WrappedErrors() []error {
return e
}
type unrecoverableError struct {
error
}
// Unrecoverable wraps an error in `unrecoverableError` struct
func Unrecoverable(err error) error {
return unrecoverableError{err}
}
// IsRecoverable checks if error is an instance of `unrecoverableError`
func IsRecoverable(err error) bool {
_, isUnrecoverable := err.(unrecoverableError)
return !isUnrecoverable
}
func unpackUnrecoverable(err error) error {
if unrecoverable, isUnrecoverable := err.(unrecoverableError); isUnrecoverable {
return unrecoverable.error
}
return err
}

View File

@@ -2,8 +2,7 @@
[![Release](https://img.shields.io/github/release/avast/retry-go.svg?style=flat-square)](https://github.com/avast/retry-go/releases/latest)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Travis](https://img.shields.io/travis/avast/retry-go.svg?style=flat-square)](https://travis-ci.org/avast/retry-go)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/fieg9gon3qlq0a9a?svg=true)](https://ci.appveyor.com/project/JaSei/retry-go)
![GitHub Actions](https://github.com/avast/retry-go/actions/workflows/workflow.yaml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/avast/retry-go)
[![GoDoc](https://godoc.org/github.com/avast/retry-go?status.svg&style=flat-square)](http://godoc.org/github.com/avast/retry-go)
[![codecov.io](https://codecov.io/github/avast/retry-go/coverage.svg?branch=master)](https://codecov.io/github/avast/retry-go?branch=master)
@@ -25,6 +24,8 @@ Try `make help` for more information.
### Before pull request
> maybe you need `make setup` in order to setup environment
please try:
* run tests (`make test`)
* run linter (`make lint`)

View File

@@ -1,37 +1,26 @@
SOURCE_FILES?=$$(go list ./... | grep -v /vendor/)
TEST_PATTERN?=.
TEST_OPTIONS?=
DEP?=$$(which dep)
VERSION?=$$(cat VERSION)
LINTER?=$$(which golangci-lint)
LINTER_VERSION=1.15.0
LINTER_VERSION=1.50.0
ifeq ($(OS),Windows_NT)
DEP_VERS=dep-windows-amd64
LINTER_FILE=golangci-lint-$(LINTER_VERSION)-windows-amd64.zip
LINTER_UNPACK= >| app.zip; unzip -j app.zip -d $$GOPATH/bin; rm app.zip
else ifeq ($(OS), Darwin)
LINTER_FILE=golangci-lint-$(LINTER_VERSION)-darwin-amd64.tar.gz
LINTER_UNPACK= | tar xzf - -C $$GOPATH/bin --wildcards --strip 1 "**/golangci-lint"
else
DEP_VERS=dep-linux-amd64
LINTER_FILE=golangci-lint-$(LINTER_VERSION)-linux-amd64.tar.gz
LINTER_UNPACK= | tar xzf - -C $$GOPATH/bin --wildcards --strip 1 "**/golangci-lint"
endif
setup:
go get -u github.com/pierrre/gotestcover
go get -u golang.org/x/tools/cmd/cover
go get -u github.com/robertkrimen/godocdown/godocdown
@if [ "$(LINTER)" = "" ]; then\
curl -L https://github.com/golangci/golangci-lint/releases/download/v$(LINTER_VERSION)/$(LINTER_FILE) $(LINTER_UNPACK) ;\
chmod +x $$GOPATH/bin/golangci-lint;\
fi
@if [ "$(DEP)" = "" ]; then\
curl -L https://github.com/golang/dep/releases/download/v0.3.1/$(DEP_VERS) >| $$GOPATH/bin/dep;\
chmod +x $$GOPATH/bin/dep;\
fi
dep ensure
go install github.com/pierrre/gotestcover@latest
go install golang.org/x/tools/cmd/cover@latest
go install github.com/robertkrimen/godocdown/godocdown@latest
go mod download
generate: ## Generate README.md
godocdown >| README.md
@@ -48,6 +37,11 @@ fmt: ## gofmt and goimports all go files
find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done
lint: ## Run all the linters
@if [ "$(LINTER)" = "" ]; then\
curl -L https://github.com/golangci/golangci-lint/releases/download/v$(LINTER_VERSION)/$(LINTER_FILE) $(LINTER_UNPACK) ;\
chmod +x $$GOPATH/bin/golangci-lint;\
fi
golangci-lint run
ci: test_and_cover_report ## Run all the tests but no linters - use https://golangci.com integration instead

View File

@@ -2,8 +2,7 @@
[![Release](https://img.shields.io/github/release/avast/retry-go.svg?style=flat-square)](https://github.com/avast/retry-go/releases/latest)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Travis](https://img.shields.io/travis/avast/retry-go.svg?style=flat-square)](https://travis-ci.org/avast/retry-go)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/fieg9gon3qlq0a9a?svg=true)](https://ci.appveyor.com/project/JaSei/retry-go)
![GitHub Actions](https://github.com/avast/retry-go/actions/workflows/workflow.yaml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/avast/retry-go)
[![GoDoc](https://godoc.org/github.com/avast/retry-go?status.svg&style=flat-square)](http://godoc.org/github.com/avast/retry-go)
[![codecov.io](https://codecov.io/github/avast/retry-go/coverage.svg?branch=master)](https://codecov.io/github/avast/retry-go?branch=master)
@@ -14,8 +13,7 @@ Simple library for retry mechanism
slightly inspired by
[Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry)
### SYNOPSIS
# SYNOPSIS
http get with retry:
@@ -37,13 +35,40 @@ http get with retry:
return nil
},
)
if err != nil {
// handle error
}
fmt.Println(body)
fmt.Println(string(body))
http get with retry with data:
url := "http://example.com"
body, err := retry.DoWithData(
func() ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
},
)
if err != nil {
// handle error
}
fmt.Println(string(body))
[next examples](https://github.com/avast/retry-go/tree/master/examples)
### SEE ALSO
# SEE ALSO
* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly
complicated interface.
@@ -61,46 +86,29 @@ 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)
# BREAKING CHANGES
### BREAKING CHANGES
* 4.0.0
3.0.0
- infinity retry is possible by set `Attempts(0)` by PR [#49](https://github.com/avast/retry-go/pull/49)
* `DelayTypeFunc` accepts a new parameter `err` - this breaking change affects
only your custom Delay Functions. This change allow [make delay functions based
on error](examples/delay_based_on_error_test.go).
* 3.0.0
1.0.2 -> 2.0.0
- `DelayTypeFunc` accepts a new parameter `err` - this breaking change affects only your custom Delay Functions. This change allow [make delay functions based on error](examples/delay_based_on_error_test.go).
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units`
anymore)
* 1.0.2 -> 2.0.0
* function `retry.Units` are removed
- argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore)
- function `retry.Units` are removed
- [more about this breaking change](https://github.com/avast/retry-go/issues/7)
* [more about this breaking change](https://github.com/avast/retry-go/issues/7)
* 0.3.0 -> 1.0.0
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`)
- `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`)
## Usage
```go
var (
DefaultAttempts = uint(10)
DefaultDelay = 100 * time.Millisecond
DefaultMaxJitter = 100 * time.Millisecond
DefaultOnRetry = func(n uint, err error) {}
DefaultRetryIf = IsRecoverable
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
DefaultLastErrorOnly = false
DefaultContext = context.Background()
)
```
#### func BackOffDelay
```go
@@ -114,6 +122,12 @@ BackOffDelay is a DelayType which increases delay between consecutive retries
func Do(retryableFunc RetryableFunc, opts ...Option) error
```
#### func DoWithData
```go
func DoWithData[T any](retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error)
```
#### func FixedDelay
```go
@@ -175,6 +189,12 @@ type Error []error
Error type represents list of errors in retry
#### func (Error) As
```go
func (e Error) As(target interface{}) bool
```
#### func (Error) Error
```go
@@ -183,6 +203,31 @@ func (e Error) Error() string
Error method return string representation of Error It is an implementation of
error interface
#### func (Error) Is
```go
func (e Error) Is(target error) bool
```
#### func (Error) Unwrap
```go
func (e Error) Unwrap() error
```
Unwrap the last error for compatibility with `errors.Unwrap()`. When you need to
unwrap all errors, you should use `WrappedErrors()` instead.
err := Do(
func() error {
return errors.New("original error")
},
Attempts(1),
)
fmt.Println(errors.Unwrap(err)) # "original error" is printed
Added in version 4.2.0.
#### func (Error) WrappedErrors
```go
@@ -214,7 +259,19 @@ Option represents an option for retry.
```go
func Attempts(attempts uint) Option
```
Attempts set count of retry default is 10
Attempts set count of retry. Setting to 0 will retry until the retried function
succeeds. default is 10
#### func AttemptsForError
```go
func AttemptsForError(attempts uint, err error) Option
```
AttemptsForError sets count of retry in case execution results in given `err`
Retries for the given `err` are also counted against total retries. The retry
will stop if any of given retries is exhausted.
added in 4.3.0
#### func Context
@@ -321,6 +378,53 @@ By default RetryIf stops execution if the error is wrapped using
}
)
#### func WithTimer
```go
func WithTimer(t Timer) Option
```
WithTimer provides a way to swap out timer module implementations. This
primarily is useful for mocking/testing, where you may not want to explicitly
wait for a set duration for retries.
example of augmenting time.After with a print statement
type struct MyTimer {}
func (t *MyTimer) After(d time.Duration) <- chan time.Time {
fmt.Print("Timer called!")
return time.After(d)
}
retry.Do(
func() error { ... },
retry.WithTimer(&MyTimer{})
)
#### func WrapContextErrorWithLastError
```go
func WrapContextErrorWithLastError(wrapContextErrorWithLastError bool) Option
```
WrapContextErrorWithLastError allows the context error to be returned wrapped
with the last error that the retried function returned. This is only applicable
when Attempts is set to 0 to retry indefinitly and when using a context to
cancel / timeout
default is false
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
retry.Do(
func() error {
...
},
retry.Context(ctx),
retry.Attempts(0),
retry.WrapContextErrorWithLastError(true),
)
#### type RetryIfFunc
```go
@@ -337,6 +441,24 @@ type RetryableFunc func() error
Function signature of retryable function
#### type RetryableFuncWithData
```go
type RetryableFuncWithData[T any] func() (T, error)
```
Function signature of retryable function with data
#### type Timer
```go
type Timer interface {
After(time.Duration) <-chan time.Time
}
```
Timer represents the timer used to track time for a retry.
## Contributing
Contributions are very much welcome.
@@ -349,6 +471,8 @@ Try `make help` for more information.
### Before pull request
> maybe you need `make setup` in order to setup environment
please try:
* run tests (`make test`)
* run linter (`make lint`)

1
vendor/github.com/avast/retry-go/v4/VERSION generated vendored Normal file
View File

@@ -0,0 +1 @@
4.5.0

View File

@@ -17,16 +17,24 @@ type OnRetryFunc func(n uint, err error)
// DelayTypeFunc is called to return the next delay to wait after the retriable function fails on `err` after `n` attempts.
type DelayTypeFunc func(n uint, err error, config *Config) time.Duration
// Timer represents the timer used to track time for a retry.
type Timer interface {
After(time.Duration) <-chan time.Time
}
type Config struct {
attempts uint
delay time.Duration
maxDelay time.Duration
maxJitter time.Duration
onRetry OnRetryFunc
retryIf RetryIfFunc
delayType DelayTypeFunc
lastErrorOnly bool
context context.Context
attempts uint
attemptsForError map[error]uint
delay time.Duration
maxDelay time.Duration
maxJitter time.Duration
onRetry OnRetryFunc
retryIf RetryIfFunc
delayType DelayTypeFunc
lastErrorOnly bool
context context.Context
timer Timer
wrapContextErrorWithLastError bool
maxBackOffN uint
}
@@ -34,6 +42,8 @@ type Config struct {
// Option represents an option for retry.
type Option func(*Config)
func emptyOption(c *Config) {}
// return the direct last error that came from the retried function
// default is false (return wrapped errors with everything)
func LastErrorOnly(lastErrorOnly bool) Option {
@@ -42,7 +52,7 @@ func LastErrorOnly(lastErrorOnly bool) Option {
}
}
// Attempts set count of retry
// Attempts set count of retry. Setting to 0 will retry until the retried function succeeds.
// default is 10
func Attempts(attempts uint) Option {
return func(c *Config) {
@@ -50,6 +60,17 @@ func Attempts(attempts uint) Option {
}
}
// AttemptsForError sets count of retry in case execution results in given `err`
// Retries for the given `err` are also counted against total retries.
// The retry will stop if any of given retries is exhausted.
//
// added in 4.3.0
func AttemptsForError(attempts uint, err error) Option {
return func(c *Config) {
c.attemptsForError[err] = attempts
}
}
// Delay set delay between retry
// default is 100ms
func Delay(delay time.Duration) Option {
@@ -76,6 +97,9 @@ func MaxJitter(maxJitter time.Duration) Option {
// DelayType set type of the delay between retries
// default is BackOff
func DelayType(delayType DelayTypeFunc) Option {
if delayType == nil {
return emptyOption
}
return func(c *Config) {
c.delayType = delayType
}
@@ -141,6 +165,9 @@ func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
// }),
// )
func OnRetry(onRetry OnRetryFunc) Option {
if onRetry == nil {
return emptyOption
}
return func(c *Config) {
c.onRetry = onRetry
}
@@ -172,6 +199,9 @@ func OnRetry(onRetry OnRetryFunc) Option {
// }
// )
func RetryIf(retryIf RetryIfFunc) Option {
if retryIf == nil {
return emptyOption
}
return func(c *Config) {
c.retryIf = retryIf
}
@@ -196,3 +226,49 @@ func Context(ctx context.Context) Option {
c.context = ctx
}
}
// WithTimer provides a way to swap out timer module implementations.
// This primarily is useful for mocking/testing, where you may not want to explicitly wait for a set duration
// for retries.
//
// example of augmenting time.After with a print statement
//
// type struct MyTimer {}
//
// func (t *MyTimer) After(d time.Duration) <- chan time.Time {
// fmt.Print("Timer called!")
// return time.After(d)
// }
//
// retry.Do(
// func() error { ... },
// retry.WithTimer(&MyTimer{})
// )
func WithTimer(t Timer) Option {
return func(c *Config) {
c.timer = t
}
}
// WrapContextErrorWithLastError allows the context error to be returned wrapped with the last error that the
// retried function returned. This is only applicable when Attempts is set to 0 to retry indefinitly and when
// using a context to cancel / timeout
//
// default is false
//
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
//
// retry.Do(
// func() error {
// ...
// },
// retry.Context(ctx),
// retry.Attempts(0),
// retry.WrapContextErrorWithLastError(true),
// )
func WrapContextErrorWithLastError(wrapContextErrorWithLastError bool) Option {
return func(c *Config) {
c.wrapContextErrorWithLastError = wrapContextErrorWithLastError
}
}

340
vendor/github.com/avast/retry-go/v4/retry.go generated vendored Normal file
View File

@@ -0,0 +1,340 @@
/*
Simple library for retry mechanism
slightly inspired by [Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry)
# SYNOPSIS
http get with retry:
url := "http://example.com"
var body []byte
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
},
)
if err != nil {
// handle error
}
fmt.Println(string(body))
http get with retry with data:
url := "http://example.com"
body, err := retry.DoWithData(
func() ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
},
)
if err != nil {
// handle error
}
fmt.Println(string(body))
[next examples](https://github.com/avast/retry-go/tree/master/examples)
# SEE ALSO
* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly complicated interface.
* [sethgrid/pester](https://github.com/sethgrid/pester) - only http retry for http calls with retries and backoff
* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Go port of the exponential backoff algorithm from Google's HTTP Client Library for Java. Really complicated interface.
* [rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go) - looks good, 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)
# BREAKING CHANGES
* 4.0.0
- infinity retry is possible by set `Attempts(0)` by PR [#49](https://github.com/avast/retry-go/pull/49)
* 3.0.0
- `DelayTypeFunc` accepts a new parameter `err` - this breaking change affects only your custom Delay Functions. This change allow [make delay functions based on error](examples/delay_based_on_error_test.go).
* 1.0.2 -> 2.0.0
- argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore)
- function `retry.Units` are removed
- [more about this breaking change](https://github.com/avast/retry-go/issues/7)
* 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
import (
"context"
"errors"
"fmt"
"strings"
"time"
)
// Function signature of retryable function
type RetryableFunc func() error
// Function signature of retryable function with data
type RetryableFuncWithData[T any] func() (T, error)
// Default timer is a wrapper around time.After
type timerImpl struct{}
func (t *timerImpl) After(d time.Duration) <-chan time.Time {
return time.After(d)
}
func Do(retryableFunc RetryableFunc, opts ...Option) error {
retryableFuncWithData := func() (any, error) {
return nil, retryableFunc()
}
_, err := DoWithData(retryableFuncWithData, opts...)
return err
}
func DoWithData[T any](retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error) {
var n uint
var emptyT T
// default
config := newDefaultRetryConfig()
// apply opts
for _, opt := range opts {
opt(config)
}
if err := config.context.Err(); err != nil {
return emptyT, err
}
// Setting attempts to 0 means we'll retry until we succeed
var lastErr error
if config.attempts == 0 {
for {
t, err := retryableFunc()
if err == nil {
return t, nil
}
if !IsRecoverable(err) {
return emptyT, err
}
if !config.retryIf(err) {
return emptyT, err
}
lastErr = err
n++
config.onRetry(n, err)
select {
case <-config.timer.After(delay(config, n, err)):
case <-config.context.Done():
if config.wrapContextErrorWithLastError {
return emptyT, Error{config.context.Err(), lastErr}
}
return emptyT, config.context.Err()
}
}
}
errorLog := Error{}
attemptsForError := make(map[error]uint, len(config.attemptsForError))
for err, attempts := range config.attemptsForError {
attemptsForError[err] = attempts
}
shouldRetry := true
for shouldRetry {
t, err := retryableFunc()
if err == nil {
return t, nil
}
errorLog = append(errorLog, unpackUnrecoverable(err))
if !config.retryIf(err) {
break
}
config.onRetry(n, err)
for errToCheck, attempts := range attemptsForError {
if errors.Is(err, errToCheck) {
attempts--
attemptsForError[errToCheck] = attempts
shouldRetry = shouldRetry && attempts > 0
}
}
// if this is last attempt - don't wait
if n == config.attempts-1 {
break
}
select {
case <-config.timer.After(delay(config, n, err)):
case <-config.context.Done():
if config.lastErrorOnly {
return emptyT, config.context.Err()
}
return emptyT, append(errorLog, config.context.Err())
}
n++
shouldRetry = shouldRetry && n < config.attempts
}
if config.lastErrorOnly {
return emptyT, errorLog.Unwrap()
}
return emptyT, errorLog
}
func newDefaultRetryConfig() *Config {
return &Config{
attempts: uint(10),
attemptsForError: make(map[error]uint),
delay: 100 * time.Millisecond,
maxJitter: 100 * time.Millisecond,
onRetry: func(n uint, err error) {},
retryIf: IsRecoverable,
delayType: CombineDelay(BackOffDelay, RandomDelay),
lastErrorOnly: false,
context: context.Background(),
timer: &timerImpl{},
}
}
// Error type represents list of errors in retry
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))
for i, l := range e {
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 (e Error) Is(target error) bool {
for _, v := range e {
if errors.Is(v, target) {
return true
}
}
return false
}
func (e Error) As(target interface{}) bool {
for _, v := range e {
if errors.As(v, target) {
return true
}
}
return false
}
/*
Unwrap the last error for compatibility with `errors.Unwrap()`.
When you need to unwrap all errors, you should use `WrappedErrors()` instead.
err := Do(
func() error {
return errors.New("original error")
},
Attempts(1),
)
fmt.Println(errors.Unwrap(err)) # "original error" is printed
Added in version 4.2.0.
*/
func (e Error) Unwrap() error {
return e[len(e)-1]
}
// WrappedErrors returns the list of errors that this Error is wrapping.
// It is an implementation of the `errwrap.Wrapper` interface
// in package [errwrap](https://github.com/hashicorp/errwrap) so that
// `retry.Error` can be used with that library.
func (e Error) WrappedErrors() []error {
return e
}
type unrecoverableError struct {
error
}
func (e unrecoverableError) Unwrap() error {
return e.error
}
// Unrecoverable wraps an error in `unrecoverableError` struct
func Unrecoverable(err error) error {
return unrecoverableError{err}
}
// IsRecoverable checks if error is an instance of `unrecoverableError`
func IsRecoverable(err error) bool {
return !errors.Is(err, unrecoverableError{})
}
// Adds support for errors.Is usage on unrecoverableError
func (unrecoverableError) Is(err error) bool {
_, isUnrecoverable := err.(unrecoverableError)
return isUnrecoverable
}
func unpackUnrecoverable(err error) error {
if unrecoverable, isUnrecoverable := err.(unrecoverableError); isUnrecoverable {
return unrecoverable.error
}
return err
}
func delay(config *Config, n uint, err error) time.Duration {
delayTime := config.delayType(n, err, config)
if config.maxDelay > 0 && delayTime > config.maxDelay {
delayTime = config.maxDelay
}
return delayTime
}

6
vendor/modules.txt vendored
View File

@@ -26,9 +26,9 @@ contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp
# github.com/acobaugh/osrelease v0.1.0
## explicit; go 1.17
github.com/acobaugh/osrelease
# github.com/avast/retry-go v3.0.0+incompatible
## explicit
github.com/avast/retry-go
# github.com/avast/retry-go/v4 v4.5.0
## explicit; go 1.18
github.com/avast/retry-go/v4
# github.com/aws/aws-sdk-go v1.44.72
## explicit; go 1.11
github.com/aws/aws-sdk-go/aws