Commit 6d8bbb5c authored by Jongmin Kim's avatar Jongmin Kim

New upstream version 1.0.2

parents
# 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
# {{ .Name }}
[![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)
[![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)
[![Sourcegraph](https://sourcegraph.com/github.com/avast/retry-go/-/badge.svg)](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
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.
language: go
go:
- 1.6
- 1.7
- 1.8
- 1.9
install:
- make setup
script:
- make ci
after_success:
- bash <(curl -s https://codecov.io/bash)
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.1.4"
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.
SOURCE_FILES?=$$(go list ./... | grep -v /vendor/)
TEST_PATTERN?=.
TEST_OPTIONS?=
DEP?=$$(which dep)
VERSION?=$$(cat VERSION)
ifeq ($(OS),Windows_NT)
DEP_VERS=dep-windows-amd64
else
DEP_VERS=dep-linux-amd64
endif
setup: ## Install all the build and lint dependencies
# fix of gopkg.in issue (https://github.com/niemeyer/gopkg/issues/50)
git config --global http.https://gopkg.in.followRedirects true
go get -u gopkg.in/alecthomas/gometalinter.v1
go get -u github.com/pierrre/gotestcover
go get -u golang.org/x/tools/cmd/cover
go get -u github.com/robertkrimen/godocdown/godocdown
gometalinter.v1 --install
@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
generate: ## Generate README.md
godocdown >| README.md
test: generate ## Run all the tests
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
gometalinter.v1 --vendor --disable-all \
--enable=deadcode \
--enable=ineffassign \
--enable=gosimple \
--enable=staticcheck \
--enable=gofmt \
--enable=goimports \
--enable=dupl \
--enable=misspell \
--enable=errcheck \
--enable=vet \
--deadline=10m \
./...
ci: test lint ## Run all the tests and code checks
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)
# 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
# retry
[![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)
[![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)
[![Sourcegraph](https://sourcegraph.com/github.com/avast/retry-go/-/badge.svg)](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
},
)
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
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 Do
```go
func Do(retryableFunc RetryableFunc, opts ...Option) error
```
#### type Error
```go
type Error []error
```
Error type represents list of errors in retry
#### 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) 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 default is 10
#### func Delay
```go
func Delay(delay time.Duration) Option
```
Delay set delay between retry default are 1e5 units
#### 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 unit, 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
})
)
#### 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 RetryableFunc
```go
type RetryableFunc func() error
```
Function signature of retryable function
## 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
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.
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
package retry_test
import (
"io/ioutil"
"net/http"
"testing"
"github.com/avast/retry-go"
"github.com/stretchr/testify/assert"
)
func TestGet(t *testing.T) {
url := "http://example.com"
var body []byte
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err == nil {
defer func() {
if err := resp.Body.Close(); err != nil {
panic(err)
}
}()
body, err = ioutil.ReadAll(resp.Body)
}
return err
},
)
assert.NoError(t, err)
assert.NotEmpty(t, body)
}
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
}
}
/*
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
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 (
"fmt"
"strings"
"time"
)
// Function signature of retryable function
type RetryableFunc func() error
func Do(retryableFunc RetryableFunc, opts ...Option) error {
var n uint
//default
config := &config{
attempts: 10,
delay: 1e5,
units: time.Microsecond,
onRetry: func(n uint, err error) {},
retryIf: func(err error) bool { return true },
}
//apply opts
for _, opt := range opts {
opt(config)
}
errorLog := make(Error, config.attempts)
for n < config.attempts {
err := retryableFunc()
if err != nil {
config.onRetry(n, err)
errorLog[n] = err
if !config.retryIf(err) {
break
}
// if this is last attempt - don't wait
if n == config.attempts-1 {
break
}
delayTime := config.delay * (1 << (n - 1))
time.Sleep((time.Duration)(delayTime) * config.units)
} else {
return nil
}
n++
}
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
}
package retry
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestDoAllFailed(t *testing.T) {
var retrySum uint
err := Do(
func() error { return errors.New("test") },
OnRetry(func(n uint, err error) { retrySum += n }),
Units(time.Nanosecond),
)
assert.Error(t, err)
expectedErrorFormat := `All attempts fail:
#1: test
#2: test
#3: test
#4: test
#5: test
#6: test
#7: test
#8: test
#9: test
#10: test`
assert.Equal(t, expectedErrorFormat, err.Error(), "retry error format")
assert.Equal(t, uint(45), retrySum, "right count of retry")
}
func TestDoFirstOk(t *testing.T) {
var retrySum uint
err := Do(
func() error { return nil },
OnRetry(func(n uint, err error) { retrySum += n }),
)
assert.NoError(t, err)
assert.Equal(t, uint(0), retrySum, "no retry")
}
func TestRetryIf(t *testing.T) {
var retryCount uint
err := Do(
func() error {
if retryCount >= 2 {
return errors.New("special")
} else {
return errors.New("test")
}
},
OnRetry(func(n uint, err error) { retryCount++ }),
RetryIf(func(err error) bool {
return err.Error() != "special"
}),
Units(time.Nanosecond),
)
assert.Error(t, err)
expectedErrorFormat := `All attempts fail:
#1: test
#2: test
#3: special`
assert.Equal(t, expectedErrorFormat, err.Error(), "retry error format")
assert.Equal(t, uint(3), retryCount, "right count of retry")
}
func TestDefaultSleep(t *testing.T) {
start := time.Now()
err := Do(
func() error { return errors.New("test") },
Attempts(3),
)
dur := time.Since(start)