Import upstream version 4.3.0+git20221115.1.affbf8f+ds
Debian Janitor
1 year, 5 months ago
0 | # Binaries for programs and plugins | |
1 | *.exe | |
2 | *.dll | |
3 | *.so | |
4 | *.dylib | |
5 | ||
6 | # Test binary, build with `go test -c` | |
7 | *.test | |
8 | ||
9 | # Output of the go coverage tool, specifically when used with LiteIDE | |
10 | *.out | |
11 | ||
12 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 | |
13 | .glide/ | |
14 | ||
15 | # dep | |
16 | vendor/ | |
17 | Gopkg.lock | |
18 | ||
19 | # cover | |
20 | coverage.txt |
1 | 1 | |
2 | 2 | [![Release](https://img.shields.io/github/release/avast/retry-go.svg?style=flat-square)](https://github.com/avast/retry-go/releases/latest) |
3 | 3 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) |
4 | [![Travis](https://img.shields.io/travis/avast/retry-go.svg?style=flat-square)](https://travis-ci.org/avast/retry-go) | |
5 | [![AppVeyor](https://ci.appveyor.com/api/projects/status/fieg9gon3qlq0a9a?svg=true)](https://ci.appveyor.com/project/JaSei/retry-go) | |
4 | ![GitHub Actions](https://github.com/avast/retry-go/actions/workflows/workflow.yaml/badge.svg) | |
6 | 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/avast/retry-go) |
7 | 6 | [![GoDoc](https://godoc.org/github.com/avast/retry-go?status.svg&style=flat-square)](http://godoc.org/github.com/avast/retry-go) |
8 | 7 | [![codecov.io](https://codecov.io/github/avast/retry-go/coverage.svg?branch=master)](https://codecov.io/github/avast/retry-go?branch=master) |
24 | 23 | |
25 | 24 | ### Before pull request |
26 | 25 | |
26 | > maybe you need `make setup` in order to setup environment | |
27 | ||
27 | 28 | please try: |
28 | 29 | * run tests (`make test`) |
29 | 30 | * run linter (`make lint`) |
0 | language: go | |
1 | ||
2 | go: | |
3 | - 1.13 | |
4 | - 1.14 | |
5 | - 1.15 | |
6 | ||
7 | install: | |
8 | - make setup | |
9 | ||
10 | script: | |
11 | - make ci | |
12 | ||
13 | after_success: | |
14 | - bash <(curl -s https://codecov.io/bash) |
2 | 2 | TEST_OPTIONS?= |
3 | 3 | VERSION?=$$(cat VERSION) |
4 | 4 | LINTER?=$$(which golangci-lint) |
5 | LINTER_VERSION=1.15.0 | |
5 | LINTER_VERSION=1.50.0 | |
6 | 6 | |
7 | 7 | ifeq ($(OS),Windows_NT) |
8 | 8 | LINTER_FILE=golangci-lint-$(LINTER_VERSION)-windows-amd64.zip |
16 | 16 | endif |
17 | 17 | |
18 | 18 | setup: |
19 | go get -u github.com/pierrre/gotestcover | |
20 | go get -u golang.org/x/tools/cmd/cover | |
21 | go get -u github.com/robertkrimen/godocdown/godocdown | |
22 | @if [ "$(LINTER)" = "" ]; then\ | |
23 | curl -L https://github.com/golangci/golangci-lint/releases/download/v$(LINTER_VERSION)/$(LINTER_FILE) $(LINTER_UNPACK) ;\ | |
24 | chmod +x $$GOPATH/bin/golangci-lint;\ | |
25 | fi | |
19 | go install github.com/pierrre/gotestcover@latest | |
20 | go install golang.org/x/tools/cmd/cover@latest | |
21 | go install github.com/robertkrimen/godocdown/godocdown@latest | |
26 | 22 | go mod download |
27 | 23 | |
28 | 24 | generate: ## Generate README.md |
40 | 36 | find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done |
41 | 37 | |
42 | 38 | lint: ## Run all the linters |
39 | @if [ "$(LINTER)" = "" ]; then\ | |
40 | curl -L https://github.com/golangci/golangci-lint/releases/download/v$(LINTER_VERSION)/$(LINTER_FILE) $(LINTER_UNPACK) ;\ | |
41 | chmod +x $$GOPATH/bin/golangci-lint;\ | |
42 | fi | |
43 | ||
43 | 44 | golangci-lint run |
44 | 45 | |
45 | 46 | ci: test_and_cover_report ## Run all the tests but no linters - use https://golangci.com integration instead |
1 | 1 | |
2 | 2 | [![Release](https://img.shields.io/github/release/avast/retry-go.svg?style=flat-square)](https://github.com/avast/retry-go/releases/latest) |
3 | 3 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) |
4 | [![Travis](https://img.shields.io/travis/avast/retry-go.svg?style=flat-square)](https://travis-ci.org/avast/retry-go) | |
5 | [![AppVeyor](https://ci.appveyor.com/api/projects/status/fieg9gon3qlq0a9a?svg=true)](https://ci.appveyor.com/project/JaSei/retry-go) | |
4 | ![GitHub Actions](https://github.com/avast/retry-go/actions/workflows/workflow.yaml/badge.svg) | |
6 | 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/avast/retry-go) |
7 | 6 | [![GoDoc](https://godoc.org/github.com/avast/retry-go?status.svg&style=flat-square)](http://godoc.org/github.com/avast/retry-go) |
8 | 7 | [![codecov.io](https://codecov.io/github/avast/retry-go/coverage.svg?branch=master)](https://codecov.io/github/avast/retry-go?branch=master) |
13 | 12 | slightly inspired by |
14 | 13 | [Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry) |
15 | 14 | |
16 | ||
17 | ### SYNOPSIS | |
15 | # SYNOPSIS | |
18 | 16 | |
19 | 17 | http get with retry: |
20 | 18 | |
41 | 39 | |
42 | 40 | [next examples](https://github.com/avast/retry-go/tree/master/examples) |
43 | 41 | |
44 | ||
45 | ### SEE ALSO | |
42 | # SEE ALSO | |
46 | 43 | |
47 | 44 | * [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly |
48 | 45 | complicated interface. |
60 | 57 | * [matryer/try](https://github.com/matryer/try) - very popular package, |
61 | 58 | nonintuitive interface (for me) |
62 | 59 | |
63 | ||
64 | ### BREAKING CHANGES | |
60 | # BREAKING CHANGES | |
65 | 61 | |
66 | 62 | * 4.0.0 |
67 | 63 | |
68 | * infinity retry is possible by set `Attempts(0)` by PR [#49](https://github.com/avast/retry-go/pull/49) | |
64 | - infinity retry is possible by set `Attempts(0)` by PR [#49](https://github.com/avast/retry-go/pull/49) | |
69 | 65 | |
70 | 66 | * 3.0.0 |
71 | 67 | |
72 | * `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). | |
68 | - `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). | |
73 | 69 | |
74 | 70 | * 1.0.2 -> 2.0.0 |
75 | 71 | |
76 | * argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore) | |
77 | * function `retry.Units` are removed | |
78 | * [more about this breaking change](https://github.com/avast/retry-go/issues/7) | |
72 | - argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore) | |
73 | - function `retry.Units` are removed | |
74 | - [more about this breaking change](https://github.com/avast/retry-go/issues/7) | |
79 | 75 | |
80 | 76 | * 0.3.0 -> 1.0.0 |
81 | 77 | |
82 | * `retry.Retry` function are changed to `retry.Do` function | |
83 | * `retry.RetryCustom` (OnRetry) and `retry.RetryCustomWithOpts` functions are now implement via functions produces Options (aka `retry.OnRetry`) | |
78 | - `retry.Retry` function are changed to `retry.Do` function | |
79 | - `retry.RetryCustom` (OnRetry) and `retry.RetryCustomWithOpts` functions are now implement via functions produces Options (aka `retry.OnRetry`) | |
84 | 80 | |
85 | 81 | ## Usage |
86 | 82 | |
158 | 154 | |
159 | 155 | Error type represents list of errors in retry |
160 | 156 | |
157 | #### func (Error) As | |
158 | ||
159 | ```go | |
160 | func (e Error) As(target interface{}) bool | |
161 | ``` | |
162 | ||
161 | 163 | #### func (Error) Error |
162 | 164 | |
163 | 165 | ```go |
165 | 167 | ``` |
166 | 168 | Error method return string representation of Error It is an implementation of |
167 | 169 | error interface |
170 | ||
171 | #### func (Error) Is | |
172 | ||
173 | ```go | |
174 | func (e Error) Is(target error) bool | |
175 | ``` | |
176 | ||
177 | #### func (Error) Unwrap | |
178 | ||
179 | ```go | |
180 | func (e Error) Unwrap() error | |
181 | ``` | |
182 | Unwrap the last error for compatible with the `errors.Unwrap()` when you need | |
183 | unwrap all erros, you should use `WrappedErrors()` instead | |
184 | ||
185 | err := Do( | |
186 | func() error { | |
187 | return errors.New("original error") | |
188 | }, | |
189 | Attempts(1), | |
190 | ) | |
191 | ||
192 | fmt.Println(errors.Unwrap(err)) # "original error" is printed | |
193 | ||
194 | added in version 4.2.0 | |
168 | 195 | |
169 | 196 | #### func (Error) WrappedErrors |
170 | 197 | |
199 | 226 | ``` |
200 | 227 | Attempts set count of retry. Setting to 0 will retry until the retried function |
201 | 228 | succeeds. default is 10 |
229 | ||
230 | #### func AttemptsForError | |
231 | ||
232 | ```go | |
233 | func AttemptsForError(attempts uint, err error) Option | |
234 | ``` | |
235 | AttemptsForError sets count of retry in case execution results in given `err` | |
236 | Retries for the given `err` are also counted against total retries. The retry | |
237 | will stop if any of given retries is exhausted. | |
238 | ||
239 | added in 4.3.0 | |
202 | 240 | |
203 | 241 | #### func Context |
204 | 242 | |
305 | 343 | } |
306 | 344 | ) |
307 | 345 | |
346 | #### func WithTimer | |
347 | ||
348 | ```go | |
349 | func WithTimer(t Timer) Option | |
350 | ``` | |
351 | WithTimer provides a way to swap out timer module implementations. This | |
352 | primarily is useful for mocking/testing, where you may not want to explicitly | |
353 | wait for a set duration for retries. | |
354 | ||
355 | example of augmenting time.After with a print statement | |
356 | ||
357 | type struct MyTimer {} | |
358 | ||
359 | func (t *MyTimer) After(d time.Duration) <- chan time.Time { | |
360 | fmt.Print("Timer called!") | |
361 | return time.After(d) | |
362 | } | |
363 | ||
364 | retry.Do( | |
365 | ||
366 | func() error { ... }, | |
367 | retry.WithTimer(&MyTimer{}) | |
368 | ||
369 | ) | |
370 | ||
308 | 371 | #### type RetryIfFunc |
309 | 372 | |
310 | 373 | ```go |
321 | 384 | |
322 | 385 | Function signature of retryable function |
323 | 386 | |
387 | #### type Timer | |
388 | ||
389 | ```go | |
390 | type Timer interface { | |
391 | After(time.Duration) <-chan time.Time | |
392 | } | |
393 | ``` | |
394 | ||
395 | Timer represents the timer used to track time for a retry. | |
396 | ||
324 | 397 | ## Contributing |
325 | 398 | |
326 | 399 | Contributions are very much welcome. |
332 | 405 | Try `make help` for more information. |
333 | 406 | |
334 | 407 | ### Before pull request |
408 | ||
409 | > maybe you need `make setup` in order to setup environment | |
335 | 410 | |
336 | 411 | please try: |
337 | 412 | * run tests (`make test`) |
0 | version: "{build}" | |
1 | ||
2 | clone_folder: c:\Users\appveyor\go\src\github.com\avast\retry-go | |
3 | ||
4 | #os: Windows Server 2012 R2 | |
5 | platform: x64 | |
6 | ||
7 | install: | |
8 | - copy c:\MinGW\bin\mingw32-make.exe c:\MinGW\bin\make.exe | |
9 | - set GOPATH=C:\Users\appveyor\go | |
10 | - set PATH=%PATH%;c:\MinGW\bin | |
11 | - set PATH=%PATH%;%GOPATH%\bin;c:\go\bin | |
12 | - set GOBIN=%GOPATH%\bin | |
13 | - go version | |
14 | - go env | |
15 | - make setup | |
16 | ||
17 | build_script: | |
18 | - make ci |
1 | 1 | |
2 | 2 | go 1.13 |
3 | 3 | |
4 | require ( | |
5 | github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0 // indirect | |
6 | github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481 // indirect | |
7 | github.com/stretchr/testify v1.7.0 | |
8 | golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 // indirect | |
9 | golang.org/x/tools v0.1.7 // indirect | |
10 | ) | |
4 | require github.com/stretchr/testify v1.8.1 |
0 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | |
1 | 0 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
2 | github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0 h1:i5VIxp6QB8oWZ8IkK8zrDgeT6ORGIUeiN+61iETwJbI= | |
3 | github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0/go.mod h1:4xpMLz7RBWyB+ElzHu8Llua96TRCB3YwX+l5EP1wmHk= | |
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
4 | 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
5 | 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
6 | github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= | |
7 | 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
8 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | |
9 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |
10 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= | |
11 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |
12 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | |
13 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | |
14 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |
15 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |
16 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | |
17 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
18 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
19 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
20 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
21 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
22 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
23 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
24 | golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
25 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |
26 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |
27 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |
28 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
29 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | |
30 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= | |
31 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |
32 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |
33 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |
6 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | |
7 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | |
8 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |
9 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | |
10 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= | |
11 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | |
34 | 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= |
35 | 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
36 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | |
37 | 14 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
15 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | |
16 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
22 | 22 | } |
23 | 23 | |
24 | 24 | type Config struct { |
25 | attempts uint | |
26 | delay time.Duration | |
27 | maxDelay time.Duration | |
28 | maxJitter time.Duration | |
29 | onRetry OnRetryFunc | |
30 | retryIf RetryIfFunc | |
31 | delayType DelayTypeFunc | |
32 | lastErrorOnly bool | |
33 | context context.Context | |
34 | timer Timer | |
25 | attempts uint | |
26 | attemptsForError map[error]uint | |
27 | delay time.Duration | |
28 | maxDelay time.Duration | |
29 | maxJitter time.Duration | |
30 | onRetry OnRetryFunc | |
31 | retryIf RetryIfFunc | |
32 | delayType DelayTypeFunc | |
33 | lastErrorOnly bool | |
34 | context context.Context | |
35 | timer Timer | |
35 | 36 | |
36 | 37 | maxBackOffN uint |
37 | 38 | } |
54 | 55 | func Attempts(attempts uint) Option { |
55 | 56 | return func(c *Config) { |
56 | 57 | c.attempts = attempts |
58 | } | |
59 | } | |
60 | ||
61 | // AttemptsForError sets count of retry in case execution results in given `err` | |
62 | // Retries for the given `err` are also counted against total retries. | |
63 | // The retry will stop if any of given retries is exhausted. | |
64 | // | |
65 | // added in 4.3.0 | |
66 | func AttemptsForError(attempts uint, err error) Option { | |
67 | return func(c *Config) { | |
68 | c.attemptsForError[err] = attempts | |
57 | 69 | } |
58 | 70 | } |
59 | 71 | |
220 | 232 | // example of augmenting time.After with a print statement |
221 | 233 | // |
222 | 234 | // type struct MyTimer {} |
223 | // func (t *MyTimer) After(d time.Duration) <- chan time.Time { | |
224 | // fmt.Print("Timer called!") | |
225 | // return time.After(d) | |
226 | // } | |
227 | // | |
235 | // | |
236 | // func (t *MyTimer) After(d time.Duration) <- chan time.Time { | |
237 | // fmt.Print("Timer called!") | |
238 | // return time.After(d) | |
239 | // } | |
228 | 240 | // |
229 | 241 | // retry.Do( |
230 | // func() error { ... }, | |
231 | // retry.WithTimer(&MyTimer{}) | |
242 | // | |
243 | // func() error { ... }, | |
244 | // retry.WithTimer(&MyTimer{}) | |
245 | // | |
232 | 246 | // ) |
233 | // | |
234 | 247 | func WithTimer(t Timer) Option { |
235 | 248 | return func(c *Config) { |
236 | 249 | c.timer = t |
2 | 2 | |
3 | 3 | slightly inspired by [Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry) |
4 | 4 | |
5 | SYNOPSIS | |
5 | # SYNOPSIS | |
6 | 6 | |
7 | 7 | http get with retry: |
8 | 8 | |
29 | 29 | |
30 | 30 | [next examples](https://github.com/avast/retry-go/tree/master/examples) |
31 | 31 | |
32 | ||
33 | SEE ALSO | |
32 | # SEE ALSO | |
34 | 33 | |
35 | 34 | * [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly complicated interface. |
36 | 35 | |
42 | 41 | |
43 | 42 | * [matryer/try](https://github.com/matryer/try) - very popular package, nonintuitive interface (for me) |
44 | 43 | |
45 | ||
46 | BREAKING CHANGES | |
44 | # BREAKING CHANGES | |
47 | 45 | |
48 | 46 | * 4.0.0 |
49 | * infinity retry is possible by set `Attempts(0)` by PR [#49](https://github.com/avast/retry-go/pull/49) | |
47 | - infinity retry is possible by set `Attempts(0)` by PR [#49](https://github.com/avast/retry-go/pull/49) | |
48 | ||
50 | 49 | * 3.0.0 |
51 | * `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). | |
50 | - `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). | |
51 | ||
52 | 52 | * 1.0.2 -> 2.0.0 |
53 | * argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore) | |
54 | * function `retry.Units` are removed | |
55 | * [more about this breaking change](https://github.com/avast/retry-go/issues/7) | |
53 | - argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore) | |
54 | - function `retry.Units` are removed | |
55 | - [more about this breaking change](https://github.com/avast/retry-go/issues/7) | |
56 | ||
56 | 57 | * 0.3.0 -> 1.0.0 |
57 | * `retry.Retry` function are changed to `retry.Do` function | |
58 | * `retry.RetryCustom` (OnRetry) and `retry.RetryCustomWithOpts` functions are now implement via functions produces Options (aka `retry.OnRetry`) | |
59 | ||
60 | ||
58 | - `retry.Retry` function are changed to `retry.Do` function | |
59 | - `retry.RetryCustom` (OnRetry) and `retry.RetryCustomWithOpts` functions are now implement via functions produces Options (aka `retry.OnRetry`) | |
61 | 60 | */ |
62 | 61 | package retry |
63 | 62 | |
64 | 63 | import ( |
65 | 64 | "context" |
65 | "errors" | |
66 | 66 | "fmt" |
67 | 67 | "strings" |
68 | 68 | "time" |
100 | 100 | |
101 | 101 | config.onRetry(n, err) |
102 | 102 | select { |
103 | case <-time.After(delay(config, n, err)): | |
103 | case <-config.timer.After(delay(config, n, err)): | |
104 | 104 | case <-config.context.Done(): |
105 | 105 | return nil |
106 | 106 | } |
116 | 116 | errorLog = make(Error, 1) |
117 | 117 | } |
118 | 118 | |
119 | attemptsForError := make(map[error]uint, len(config.attemptsForError)) | |
120 | for err, attempts := range config.attemptsForError { | |
121 | attemptsForError[err] = attempts | |
122 | } | |
123 | ||
119 | 124 | lastErrIndex := n |
120 | for n < config.attempts { | |
125 | shouldRetry := true | |
126 | for shouldRetry { | |
121 | 127 | err := retryableFunc() |
122 | 128 | |
123 | 129 | if err != nil { |
128 | 134 | } |
129 | 135 | |
130 | 136 | config.onRetry(n, err) |
137 | ||
138 | for errToCheck, attempts := range attemptsForError { | |
139 | if errors.Is(err, errToCheck) { | |
140 | attempts-- | |
141 | attemptsForError[errToCheck] = attempts | |
142 | shouldRetry = shouldRetry && attempts > 0 | |
143 | } | |
144 | } | |
131 | 145 | |
132 | 146 | // if this is last attempt - don't wait |
133 | 147 | if n == config.attempts-1 { |
140 | 154 | if config.lastErrorOnly { |
141 | 155 | return config.context.Err() |
142 | 156 | } |
157 | n++ | |
143 | 158 | errorLog[n] = config.context.Err() |
144 | 159 | return errorLog |
145 | 160 | } |
149 | 164 | } |
150 | 165 | |
151 | 166 | n++ |
167 | shouldRetry = shouldRetry && n < config.attempts | |
168 | ||
152 | 169 | if !config.lastErrorOnly { |
153 | 170 | lastErrIndex = n |
154 | 171 | } |
162 | 179 | |
163 | 180 | func newDefaultRetryConfig() *Config { |
164 | 181 | return &Config{ |
165 | attempts: uint(10), | |
166 | delay: 100 * time.Millisecond, | |
167 | maxJitter: 100 * time.Millisecond, | |
168 | onRetry: func(n uint, err error) {}, | |
169 | retryIf: IsRecoverable, | |
170 | delayType: CombineDelay(BackOffDelay, RandomDelay), | |
171 | lastErrorOnly: false, | |
172 | context: context.Background(), | |
173 | timer: &timerImpl{}, | |
182 | attempts: uint(10), | |
183 | attemptsForError: make(map[error]uint), | |
184 | delay: 100 * time.Millisecond, | |
185 | maxJitter: 100 * time.Millisecond, | |
186 | onRetry: func(n uint, err error) {}, | |
187 | retryIf: IsRecoverable, | |
188 | delayType: CombineDelay(BackOffDelay, RandomDelay), | |
189 | lastErrorOnly: false, | |
190 | context: context.Background(), | |
191 | timer: &timerImpl{}, | |
174 | 192 | } |
175 | 193 | } |
176 | 194 | |
190 | 208 | return fmt.Sprintf("All attempts fail:\n%s", strings.Join(logWithNumber, "\n")) |
191 | 209 | } |
192 | 210 | |
211 | func (e Error) Is(target error) bool { | |
212 | for _, v := range e { | |
213 | if errors.Is(v, target) { | |
214 | return true | |
215 | } | |
216 | } | |
217 | return false | |
218 | } | |
219 | ||
220 | func (e Error) As(target interface{}) bool { | |
221 | for _, v := range e { | |
222 | if errors.As(v, target) { | |
223 | return true | |
224 | } | |
225 | } | |
226 | return false | |
227 | } | |
228 | ||
229 | /* | |
230 | Unwrap the last error for compatible with the `errors.Unwrap()` | |
231 | when you need unwrap all erros, you should use `WrappedErrors()` instead | |
232 | ||
233 | err := Do( | |
234 | func() error { | |
235 | return errors.New("original error") | |
236 | }, | |
237 | Attempts(1), | |
238 | ) | |
239 | ||
240 | fmt.Println(errors.Unwrap(err)) # "original error" is printed | |
241 | ||
242 | added in version 4.2.0 | |
243 | */ | |
244 | func (e Error) Unwrap() error { | |
245 | return e[len(e)-1] | |
246 | } | |
247 | ||
193 | 248 | func lenWithoutNil(e Error) (count int) { |
194 | 249 | for _, v := range e { |
195 | 250 | if v != nil { |
3 | 3 | "context" |
4 | 4 | "errors" |
5 | 5 | "fmt" |
6 | "os" | |
6 | 7 | "testing" |
7 | 8 | "time" |
8 | 9 | |
108 | 109 | assert.Equal(t, count, 1) |
109 | 110 | } |
110 | 111 | |
112 | func TestAttemptsForError(t *testing.T) { | |
113 | count := uint(0) | |
114 | testErr := os.ErrInvalid | |
115 | attemptsForTestError := uint(3) | |
116 | err := Do( | |
117 | func() error { | |
118 | count++ | |
119 | return testErr | |
120 | }, | |
121 | AttemptsForError(attemptsForTestError, testErr), | |
122 | Attempts(5), | |
123 | ) | |
124 | assert.Error(t, err) | |
125 | assert.Equal(t, attemptsForTestError, count) | |
126 | } | |
127 | ||
111 | 128 | func TestDefaultSleep(t *testing.T) { |
112 | 129 | start := time.Now() |
113 | 130 | err := Do( |
116 | 133 | ) |
117 | 134 | dur := time.Since(start) |
118 | 135 | assert.Error(t, err) |
119 | assert.True(t, dur > 300*time.Millisecond, "3 times default retry is longer then 300ms") | |
136 | assert.Greater(t, dur, 300*time.Millisecond, "3 times default retry is longer then 300ms") | |
120 | 137 | } |
121 | 138 | |
122 | 139 | func TestFixedSleep(t *testing.T) { |
128 | 145 | ) |
129 | 146 | dur := time.Since(start) |
130 | 147 | assert.Error(t, err) |
131 | assert.True(t, dur < 500*time.Millisecond, "3 times default retry is shorter then 500ms") | |
148 | assert.Less(t, dur, 500*time.Millisecond, "3 times default retry is shorter then 500ms") | |
132 | 149 | } |
133 | 150 | |
134 | 151 | func TestLastErrorOnly(t *testing.T) { |
159 | 176 | } |
160 | 177 | |
161 | 178 | func TestCombineFixedDelays(t *testing.T) { |
179 | if os.Getenv("OS") == "macos-latest" { | |
180 | t.Skip("Skipping testing in MacOS GitHub actions - too slow, duration is wrong") | |
181 | } | |
182 | ||
162 | 183 | start := time.Now() |
163 | 184 | err := Do( |
164 | 185 | func() error { return errors.New("test") }, |
167 | 188 | ) |
168 | 189 | dur := time.Since(start) |
169 | 190 | assert.Error(t, err) |
170 | assert.True(t, dur > 400*time.Millisecond, "3 times combined, fixed retry is longer then 400ms") | |
171 | assert.True(t, dur < 500*time.Millisecond, "3 times combined, fixed retry is shorter then 500ms") | |
191 | assert.Greater(t, dur, 400*time.Millisecond, "3 times combined, fixed retry is greater then 400ms") | |
192 | assert.Less(t, dur, 500*time.Millisecond, "3 times combined, fixed retry is less then 500ms") | |
172 | 193 | } |
173 | 194 | |
174 | 195 | func TestRandomDelay(t *testing.T) { |
196 | if os.Getenv("OS") == "macos-latest" { | |
197 | t.Skip("Skipping testing in MacOS GitHub actions - too slow, duration is wrong") | |
198 | } | |
199 | ||
175 | 200 | start := time.Now() |
176 | 201 | err := Do( |
177 | 202 | func() error { return errors.New("test") }, |
181 | 206 | ) |
182 | 207 | dur := time.Since(start) |
183 | 208 | assert.Error(t, err) |
184 | assert.True(t, dur > 2*time.Millisecond, "3 times random retry is longer then 2ms") | |
185 | assert.True(t, dur < 100*time.Millisecond, "3 times random retry is shorter then 100ms") | |
209 | assert.Greater(t, dur, 2*time.Millisecond, "3 times random retry is longer then 2ms") | |
210 | assert.Less(t, dur, 150*time.Millisecond, "3 times random retry is shorter then 150ms") | |
186 | 211 | } |
187 | 212 | |
188 | 213 | func TestMaxDelay(t *testing.T) { |
214 | if os.Getenv("OS") == "macos-latest" { | |
215 | t.Skip("Skipping testing in MacOS GitHub actions - too slow, duration is wrong") | |
216 | } | |
217 | ||
189 | 218 | start := time.Now() |
190 | 219 | err := Do( |
191 | 220 | func() error { return errors.New("test") }, |
195 | 224 | ) |
196 | 225 | dur := time.Since(start) |
197 | 226 | assert.Error(t, err) |
198 | assert.True(t, dur > 120*time.Millisecond, "5 times with maximum delay retry is longer than 120ms") | |
199 | assert.True(t, dur < 205*time.Millisecond, "5 times with maximum delay retry is shorter than 205ms") | |
227 | assert.Greater(t, dur, 120*time.Millisecond, "5 times with maximum delay retry is less than 120ms") | |
228 | assert.Less(t, dur, 250*time.Millisecond, "5 times with maximum delay retry is longer than 250ms") | |
200 | 229 | } |
201 | 230 | |
202 | 231 | func TestBackOffDelay(t *testing.T) { |
334 | 363 | |
335 | 364 | expectedErrorFormat := `All attempts fail: |
336 | 365 | #1: test |
337 | #2: context canceled` | |
366 | #2: test | |
367 | #3: context canceled` | |
338 | 368 | assert.Equal(t, expectedErrorFormat, err.Error(), "retry error format") |
339 | 369 | assert.Equal(t, 2, retrySum, "called at most once") |
340 | 370 | }) |
421 | 451 | assert.Error(t, err) |
422 | 452 | |
423 | 453 | } |
454 | ||
455 | func TestErrorIs(t *testing.T) { | |
456 | var e Error | |
457 | expectErr := errors.New("error") | |
458 | closedErr := os.ErrClosed | |
459 | e = append(e, expectErr) | |
460 | e = append(e, closedErr) | |
461 | ||
462 | assert.True(t, errors.Is(e, expectErr)) | |
463 | assert.True(t, errors.Is(e, closedErr)) | |
464 | assert.False(t, errors.Is(e, errors.New("error"))) | |
465 | } | |
466 | ||
467 | type fooErr struct{ str string } | |
468 | ||
469 | func (e fooErr) Error() string { | |
470 | return e.str | |
471 | } | |
472 | ||
473 | type barErr struct{ str string } | |
474 | ||
475 | func (e barErr) Error() string { | |
476 | return e.str | |
477 | } | |
478 | ||
479 | func TestErrorAs(t *testing.T) { | |
480 | var e Error | |
481 | fe := fooErr{str: "foo"} | |
482 | e = append(e, fe) | |
483 | ||
484 | var tf fooErr | |
485 | var tb barErr | |
486 | ||
487 | assert.True(t, errors.As(e, &tf)) | |
488 | assert.False(t, errors.As(e, &tb)) | |
489 | assert.Equal(t, "foo", tf.str) | |
490 | } | |
491 | ||
492 | func TestUnwrap(t *testing.T) { | |
493 | testError := errors.New("test error") | |
494 | err := Do( | |
495 | func() error { | |
496 | return testError | |
497 | }, | |
498 | Attempts(1), | |
499 | ) | |
500 | ||
501 | assert.Error(t, err) | |
502 | assert.Equal(t, testError, errors.Unwrap(err)) | |
503 | } |