21
vendor/github.com/avast/retry-go/v4/.gitignore
generated
vendored
Normal file
21
vendor/github.com/avast/retry-go/v4/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# dep
|
||||
vendor/
|
||||
Gopkg.lock
|
||||
|
||||
# cover
|
||||
coverage.txt
|
||||
38
vendor/github.com/avast/retry-go/v4/.godocdown.tmpl
generated
vendored
Normal file
38
vendor/github.com/avast/retry-go/v4/.godocdown.tmpl
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# {{ .Name }}
|
||||
|
||||
[](https://github.com/avast/retry-go/releases/latest)
|
||||
[](LICENSE.md)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/avast/retry-go)
|
||||
[](http://godoc.org/github.com/avast/retry-go)
|
||||
[](https://codecov.io/github/avast/retry-go?branch=master)
|
||||
[](https://sourcegraph.com/github.com/avast/retry-go?badge)
|
||||
|
||||
{{ .EmitSynopsis }}
|
||||
|
||||
{{ .EmitUsage }}
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are very much welcome.
|
||||
|
||||
### Makefile
|
||||
|
||||
Makefile provides several handy rules, like README.md `generator` , `setup` for prepare build/dev environment, `test`, `cover`, etc...
|
||||
|
||||
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`)
|
||||
* if your IDE don't automaticaly do `go fmt`, run `go fmt` (`make fmt`)
|
||||
|
||||
### README
|
||||
|
||||
README.md are generate from template [.godocdown.tmpl](.godocdown.tmpl) and code documentation via [godocdown](https://github.com/robertkrimen/godocdown).
|
||||
|
||||
Never edit README.md direct, because your change will be lost.
|
||||
21
vendor/github.com/avast/retry-go/v4/LICENSE
generated
vendored
Normal file
21
vendor/github.com/avast/retry-go/v4/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Avast
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
59
vendor/github.com/avast/retry-go/v4/Makefile
generated
vendored
Normal file
59
vendor/github.com/avast/retry-go/v4/Makefile
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
SOURCE_FILES?=$$(go list ./... | grep -v /vendor/)
|
||||
TEST_PATTERN?=.
|
||||
TEST_OPTIONS?=
|
||||
VERSION?=$$(cat VERSION)
|
||||
LINTER?=$$(which golangci-lint)
|
||||
LINTER_VERSION=1.50.0
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
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
|
||||
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 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
|
||||
|
||||
test: generate test_and_cover_report lint
|
||||
|
||||
test_and_cover_report:
|
||||
gotestcover $(TEST_OPTIONS) -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m
|
||||
|
||||
cover: test ## Run all the tests and opens the coverage report
|
||||
go tool cover -html=coverage.txt
|
||||
|
||||
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
|
||||
|
||||
build:
|
||||
go build
|
||||
|
||||
release: ## Release new version
|
||||
git tag | grep -q $(VERSION) && echo This version was released! Increase VERSION! || git tag $(VERSION) && git push origin $(VERSION) && git tag v$(VERSION) && git push origin v$(VERSION)
|
||||
|
||||
# Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
.DEFAULT_GOAL := build
|
||||
485
vendor/github.com/avast/retry-go/v4/README.md
generated
vendored
Normal file
485
vendor/github.com/avast/retry-go/v4/README.md
generated
vendored
Normal file
@@ -0,0 +1,485 @@
|
||||
# retry
|
||||
|
||||
[](https://github.com/avast/retry-go/releases/latest)
|
||||
[](LICENSE.md)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/avast/retry-go)
|
||||
[](http://godoc.org/github.com/avast/retry-go)
|
||||
[](https://codecov.io/github/avast/retry-go?branch=master)
|
||||
[](https://sourcegraph.com/github.com/avast/retry-go?badge)
|
||||
|
||||
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`)
|
||||
|
||||
## Usage
|
||||
|
||||
#### func BackOffDelay
|
||||
|
||||
```go
|
||||
func BackOffDelay(n uint, _ error, config *Config) time.Duration
|
||||
```
|
||||
BackOffDelay is a DelayType which increases delay between consecutive retries
|
||||
|
||||
#### func Do
|
||||
|
||||
```go
|
||||
func Do(retryableFunc RetryableFunc, opts ...Option) error
|
||||
```
|
||||
|
||||
#### func DoWithData
|
||||
|
||||
```go
|
||||
func DoWithData[T any](retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error)
|
||||
```
|
||||
|
||||
#### func FixedDelay
|
||||
|
||||
```go
|
||||
func FixedDelay(_ uint, _ error, config *Config) time.Duration
|
||||
```
|
||||
FixedDelay is a DelayType which keeps delay the same through all iterations
|
||||
|
||||
#### func IsRecoverable
|
||||
|
||||
```go
|
||||
func IsRecoverable(err error) bool
|
||||
```
|
||||
IsRecoverable checks if error is an instance of `unrecoverableError`
|
||||
|
||||
#### func RandomDelay
|
||||
|
||||
```go
|
||||
func RandomDelay(_ uint, _ error, config *Config) time.Duration
|
||||
```
|
||||
RandomDelay is a DelayType which picks a random delay up to config.maxJitter
|
||||
|
||||
#### func Unrecoverable
|
||||
|
||||
```go
|
||||
func Unrecoverable(err error) error
|
||||
```
|
||||
Unrecoverable wraps an error in `unrecoverableError` struct
|
||||
|
||||
#### type Config
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### type DelayTypeFunc
|
||||
|
||||
```go
|
||||
type DelayTypeFunc func(n uint, err error, config *Config) time.Duration
|
||||
```
|
||||
|
||||
DelayTypeFunc is called to return the next delay to wait after the retriable
|
||||
function fails on `err` after `n` attempts.
|
||||
|
||||
#### 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
|
||||
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
|
||||
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
|
||||
func (e Error) WrappedErrors() []error
|
||||
```
|
||||
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.
|
||||
|
||||
#### type OnRetryFunc
|
||||
|
||||
```go
|
||||
type OnRetryFunc func(n uint, err error)
|
||||
```
|
||||
|
||||
Function signature of OnRetry function n = count of attempts
|
||||
|
||||
#### type Option
|
||||
|
||||
```go
|
||||
type Option func(*Config)
|
||||
```
|
||||
|
||||
Option represents an option for retry.
|
||||
|
||||
#### func Attempts
|
||||
|
||||
```go
|
||||
func Attempts(attempts uint) Option
|
||||
```
|
||||
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
|
||||
|
||||
```go
|
||||
func Context(ctx context.Context) Option
|
||||
```
|
||||
Context allow to set context of retry default are Background context
|
||||
|
||||
example of immediately cancellation (maybe it isn't the best example, but it
|
||||
describes behavior enough; I hope)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
retry.Do(
|
||||
func() error {
|
||||
...
|
||||
},
|
||||
retry.Context(ctx),
|
||||
)
|
||||
|
||||
#### func Delay
|
||||
|
||||
```go
|
||||
func Delay(delay time.Duration) Option
|
||||
```
|
||||
Delay set delay between retry default is 100ms
|
||||
|
||||
#### func DelayType
|
||||
|
||||
```go
|
||||
func DelayType(delayType DelayTypeFunc) Option
|
||||
```
|
||||
DelayType set type of the delay between retries default is BackOff
|
||||
|
||||
#### func LastErrorOnly
|
||||
|
||||
```go
|
||||
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 MaxDelay
|
||||
|
||||
```go
|
||||
func MaxDelay(maxDelay time.Duration) Option
|
||||
```
|
||||
MaxDelay set maximum delay between retry does not apply by default
|
||||
|
||||
#### func MaxJitter
|
||||
|
||||
```go
|
||||
func MaxJitter(maxJitter time.Duration) Option
|
||||
```
|
||||
MaxJitter sets the maximum random Jitter between retries for RandomDelay
|
||||
|
||||
#### func OnRetry
|
||||
|
||||
```go
|
||||
func OnRetry(onRetry OnRetryFunc) Option
|
||||
```
|
||||
OnRetry function callback are called each retry
|
||||
|
||||
log each retry example:
|
||||
|
||||
retry.Do(
|
||||
func() error {
|
||||
return errors.New("some error")
|
||||
},
|
||||
retry.OnRetry(func(n uint, err error) {
|
||||
log.Printf("#%d: %s\n", n, err)
|
||||
}),
|
||||
)
|
||||
|
||||
#### func RetryIf
|
||||
|
||||
```go
|
||||
func RetryIf(retryIf RetryIfFunc) Option
|
||||
```
|
||||
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
|
||||
}),
|
||||
)
|
||||
|
||||
By default RetryIf stops execution if the error is wrapped using
|
||||
`retry.Unrecoverable`, so above example may also be shortened to:
|
||||
|
||||
retry.Do(
|
||||
func() error {
|
||||
return retry.Unrecoverable(errors.New("special error"))
|
||||
}
|
||||
)
|
||||
|
||||
#### 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
|
||||
type RetryIfFunc func(error) bool
|
||||
```
|
||||
|
||||
Function signature of retry if function
|
||||
|
||||
#### type RetryableFunc
|
||||
|
||||
```go
|
||||
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.
|
||||
|
||||
### Makefile
|
||||
|
||||
Makefile provides several handy rules, like README.md `generator` , `setup` for prepare build/dev environment, `test`, `cover`, etc...
|
||||
|
||||
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`)
|
||||
* if your IDE don't automaticaly do `go fmt`, run `go fmt` (`make fmt`)
|
||||
|
||||
### README
|
||||
|
||||
README.md are generate from template [.godocdown.tmpl](.godocdown.tmpl) and code documentation via [godocdown](https://github.com/robertkrimen/godocdown).
|
||||
|
||||
Never edit README.md direct, because your change will be lost.
|
||||
1
vendor/github.com/avast/retry-go/v4/VERSION
generated
vendored
Normal file
1
vendor/github.com/avast/retry-go/v4/VERSION
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
4.5.1
|
||||
274
vendor/github.com/avast/retry-go/v4/options.go
generated
vendored
Normal file
274
vendor/github.com/avast/retry-go/v4/options.go
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
package retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"math/rand"
|
||||
"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)
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return func(c *Config) {
|
||||
c.lastErrorOnly = lastErrorOnly
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
c.attempts = attempts
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return func(c *Config) {
|
||||
c.delay = delay
|
||||
}
|
||||
}
|
||||
|
||||
// MaxDelay set maximum delay between retry
|
||||
// does not apply by default
|
||||
func MaxDelay(maxDelay time.Duration) Option {
|
||||
return func(c *Config) {
|
||||
c.maxDelay = maxDelay
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if delayType == nil {
|
||||
return emptyOption
|
||||
}
|
||||
return func(c *Config) {
|
||||
c.delayType = delayType
|
||||
}
|
||||
}
|
||||
|
||||
// BackOffDelay is a DelayType which increases delay between consecutive retries
|
||||
func BackOffDelay(n uint, _ error, config *Config) time.Duration {
|
||||
// 1 << 63 would overflow signed int64 (time.Duration), thus 62.
|
||||
const max uint = 62
|
||||
|
||||
if config.maxBackOffN == 0 {
|
||||
if config.delay <= 0 {
|
||||
config.delay = 1
|
||||
}
|
||||
|
||||
config.maxBackOffN = max - uint(math.Floor(math.Log2(float64(config.delay))))
|
||||
}
|
||||
|
||||
if n > config.maxBackOffN {
|
||||
n = config.maxBackOffN
|
||||
}
|
||||
|
||||
return config.delay << n
|
||||
}
|
||||
|
||||
// FixedDelay is a DelayType which keeps delay the same through all iterations
|
||||
func FixedDelay(_ uint, _ error, config *Config) time.Duration {
|
||||
return config.delay
|
||||
}
|
||||
|
||||
// RandomDelay is a DelayType which picks a random delay up to config.maxJitter
|
||||
func RandomDelay(_ uint, _ error, 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 {
|
||||
const maxInt64 = uint64(math.MaxInt64)
|
||||
|
||||
return func(n uint, err error, config *Config) time.Duration {
|
||||
var total uint64
|
||||
for _, delay := range delays {
|
||||
total += uint64(delay(n, err, config))
|
||||
if total > maxInt64 {
|
||||
total = maxInt64
|
||||
}
|
||||
}
|
||||
|
||||
return time.Duration(total)
|
||||
}
|
||||
}
|
||||
|
||||
// OnRetry function callback are called each retry
|
||||
//
|
||||
// log each retry example:
|
||||
//
|
||||
// retry.Do(
|
||||
// func() error {
|
||||
// return errors.New("some error")
|
||||
// },
|
||||
// retry.OnRetry(func(n uint, err error) {
|
||||
// log.Printf("#%d: %s\n", n, err)
|
||||
// }),
|
||||
// )
|
||||
func OnRetry(onRetry OnRetryFunc) Option {
|
||||
if onRetry == nil {
|
||||
return emptyOption
|
||||
}
|
||||
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
|
||||
// })
|
||||
// )
|
||||
//
|
||||
// By default RetryIf stops execution if the error is wrapped using `retry.Unrecoverable`,
|
||||
// so above example may also be shortened to:
|
||||
//
|
||||
// retry.Do(
|
||||
// func() error {
|
||||
// return retry.Unrecoverable(errors.New("special error"))
|
||||
// }
|
||||
// )
|
||||
func RetryIf(retryIf RetryIfFunc) Option {
|
||||
if retryIf == nil {
|
||||
return emptyOption
|
||||
}
|
||||
return func(c *Config) {
|
||||
c.retryIf = retryIf
|
||||
}
|
||||
}
|
||||
|
||||
// Context allow to set context of retry
|
||||
// default are Background context
|
||||
//
|
||||
// example of immediately cancellation (maybe it isn't the best example, but it describes behavior enough; I hope)
|
||||
//
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// cancel()
|
||||
//
|
||||
// retry.Do(
|
||||
// func() error {
|
||||
// ...
|
||||
// },
|
||||
// retry.Context(ctx),
|
||||
// )
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(c *Config) {
|
||||
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
|
||||
}
|
||||
}
|
||||
347
vendor/github.com/avast/retry-go/v4/retry.go
generated
vendored
Normal file
347
vendor/github.com/avast/retry-go/v4/retry.go
generated
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
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) Error() string {
|
||||
if e.error == nil {
|
||||
return "unrecoverable error"
|
||||
}
|
||||
return e.error.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
|
||||
}
|
||||
Reference in New Issue
Block a user