Update upstream source from tag 'upstream/2.4.3'
Update to upstream version '2.4.3'
with Debian dir 267ee032d725bc5e3242d47f7397116cf362757c
Dmitry Smirnov
4 years ago
2 | 2 | TEST_OPTIONS?= |
3 | 3 | DEP?=$$(which dep) |
4 | 4 | VERSION?=$$(cat VERSION) |
5 | LINTER?=$$(which golangci-lint) | |
6 | LINTER_VERSION=1.15.0 | |
5 | 7 | |
6 | 8 | ifeq ($(OS),Windows_NT) |
7 | 9 | DEP_VERS=dep-windows-amd64 |
10 | LINTER_FILE=golangci-lint-$(LINTER_VERSION)-windows-amd64.zip | |
11 | LINTER_UNPACK= >| app.zip; unzip -j app.zip -d $$GOPATH/bin; rm app.zip | |
12 | else ifeq ($(OS), Darwin) | |
13 | LINTER_FILE=golangci-lint-$(LINTER_VERSION)-darwin-amd64.tar.gz | |
14 | LINTER_UNPACK= | tar xzf - -C $$GOPATH/bin --wildcards --strip 1 "**/golangci-lint" | |
8 | 15 | else |
9 | 16 | DEP_VERS=dep-linux-amd64 |
17 | LINTER_FILE=golangci-lint-$(LINTER_VERSION)-linux-amd64.tar.gz | |
18 | LINTER_UNPACK= | tar xzf - -C $$GOPATH/bin --wildcards --strip 1 "**/golangci-lint" | |
10 | 19 | endif |
11 | 20 | |
12 | setup: ## Install all the build and lint dependencies | |
13 | # fix of gopkg.in issue (https://github.com/niemeyer/gopkg/issues/50) | |
14 | git config --global http.https://gopkg.in.followRedirects true | |
15 | go get -u gopkg.in/alecthomas/gometalinter.v1 | |
21 | setup: | |
16 | 22 | go get -u github.com/pierrre/gotestcover |
17 | 23 | go get -u golang.org/x/tools/cmd/cover |
18 | 24 | go get -u github.com/robertkrimen/godocdown/godocdown |
19 | gometalinter.v1 --install | |
25 | @if [ "$(LINTER)" = "" ]; then\ | |
26 | curl -L https://github.com/golangci/golangci-lint/releases/download/v$(LINTER_VERSION)/$(LINTER_FILE) $(LINTER_UNPACK) ;\ | |
27 | chmod +x $$GOPATH/bin/golangci-lint;\ | |
28 | fi | |
20 | 29 | @if [ "$(DEP)" = "" ]; then\ |
21 | 30 | curl -L https://github.com/golang/dep/releases/download/v0.3.1/$(DEP_VERS) >| $$GOPATH/bin/dep;\ |
22 | 31 | chmod +x $$GOPATH/bin/dep;\ |
26 | 35 | generate: ## Generate README.md |
27 | 36 | godocdown >| README.md |
28 | 37 | |
29 | test: generate ## Run all the tests | |
38 | test: generate test_and_cover_report lint | |
39 | ||
40 | test_and_cover_report: | |
30 | 41 | gotestcover $(TEST_OPTIONS) -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m |
31 | 42 | |
32 | 43 | cover: test ## Run all the tests and opens the coverage report |
36 | 47 | find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done |
37 | 48 | |
38 | 49 | lint: ## Run all the linters |
39 | gometalinter.v1 --vendor --disable-all \ | |
40 | --enable=deadcode \ | |
41 | --enable=ineffassign \ | |
42 | --enable=gosimple \ | |
43 | --enable=staticcheck \ | |
44 | --enable=gofmt \ | |
45 | --enable=goimports \ | |
46 | --enable=dupl \ | |
47 | --enable=misspell \ | |
48 | --enable=errcheck \ | |
49 | --enable=vet \ | |
50 | --deadline=10m \ | |
51 | ./... | |
50 | golangci-lint run | |
52 | 51 | |
53 | ci: test lint ## Run all the tests and code checks | |
52 | ci: test_and_cover_report ## Run all the tests but no linters - use https://golangci.com integration instead | |
54 | 53 | |
55 | 54 | build: |
56 | 55 | go build |
57 | 56 | |
58 | 57 | release: ## Release new version |
59 | git tag | grep -q $(VERSION) && echo This version was released! Increase VERSION! || git tag $(VERSION) && git push origin $(VERSION) | |
58 | 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) | |
60 | 59 | |
61 | 60 | # Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html |
62 | 61 | help: |
63 | 63 | |
64 | 64 | ### BREAKING CHANGES |
65 | 65 | |
66 | 1.0.2 -> 2.0.0 | |
67 | ||
68 | * argument of `retry.Delay` is final delay (no multiplication by `retry.Units` | |
69 | anymore) | |
70 | ||
71 | * function `retry.Units` are removed | |
72 | ||
73 | * [more about this breaking change](https://github.com/avast/retry-go/issues/7) | |
74 | ||
66 | 75 | 0.3.0 -> 1.0.0 |
67 | 76 | |
68 | 77 | * `retry.Retry` function are changed to `retry.Do` function |
72 | 81 | |
73 | 82 | ## Usage |
74 | 83 | |
84 | #### func BackOffDelay | |
85 | ||
86 | ```go | |
87 | func BackOffDelay(n uint, config *Config) time.Duration | |
88 | ``` | |
89 | BackOffDelay is a DelayType which increases delay between consecutive retries | |
90 | ||
75 | 91 | #### func Do |
76 | 92 | |
77 | 93 | ```go |
78 | 94 | func Do(retryableFunc RetryableFunc, opts ...Option) error |
79 | 95 | ``` |
96 | ||
97 | #### func FixedDelay | |
98 | ||
99 | ```go | |
100 | func FixedDelay(_ uint, config *Config) time.Duration | |
101 | ``` | |
102 | FixedDelay is a DelayType which keeps delay the same through all iterations | |
103 | ||
104 | #### func IsRecoverable | |
105 | ||
106 | ```go | |
107 | func IsRecoverable(err error) bool | |
108 | ``` | |
109 | IsRecoverable checks if error is an instance of `unrecoverableError` | |
110 | ||
111 | #### func Unrecoverable | |
112 | ||
113 | ```go | |
114 | func Unrecoverable(err error) unrecoverableError | |
115 | ``` | |
116 | Unrecoverable wraps an error in `unrecoverableError` struct | |
117 | ||
118 | #### type Config | |
119 | ||
120 | ```go | |
121 | type Config struct { | |
122 | } | |
123 | ``` | |
124 | ||
125 | ||
126 | #### type DelayTypeFunc | |
127 | ||
128 | ```go | |
129 | type DelayTypeFunc func(n uint, config *Config) time.Duration | |
130 | ``` | |
131 | ||
80 | 132 | |
81 | 133 | #### type Error |
82 | 134 | |
115 | 167 | #### type Option |
116 | 168 | |
117 | 169 | ```go |
118 | type Option func(*config) | |
170 | type Option func(*Config) | |
119 | 171 | ``` |
120 | 172 | |
121 | 173 | Option represents an option for retry. |
132 | 184 | ```go |
133 | 185 | func Delay(delay time.Duration) Option |
134 | 186 | ``` |
135 | Delay set delay between retry default are 1e5 units | |
187 | Delay set delay between retry default is 100ms | |
188 | ||
189 | #### func DelayType | |
190 | ||
191 | ```go | |
192 | func DelayType(delayType DelayTypeFunc) Option | |
193 | ``` | |
194 | DelayType set type of the delay between retries default is BackOff | |
195 | ||
196 | #### func LastErrorOnly | |
197 | ||
198 | ```go | |
199 | func LastErrorOnly(lastErrorOnly bool) Option | |
200 | ``` | |
201 | return the direct last error that came from the retried function default is | |
202 | false (return wrapped errors with everything) | |
136 | 203 | |
137 | 204 | #### func OnRetry |
138 | 205 | |
147 | 214 | func() error { |
148 | 215 | return errors.New("some error") |
149 | 216 | }, |
150 | retry.OnRetry(func(n unit, err error) { | |
217 | retry.OnRetry(func(n uint, err error) { | |
151 | 218 | log.Printf("#%d: %s\n", n, err) |
152 | 219 | }), |
153 | 220 | ) |
174 | 241 | }) |
175 | 242 | ) |
176 | 243 | |
177 | #### func Units | |
178 | ||
179 | ```go | |
180 | func Units(units time.Duration) Option | |
181 | ``` | |
182 | Units set unit of delay (probably only for tests purpose) default are | |
183 | microsecond | |
244 | The default RetryIf stops execution if the error is wrapped using | |
245 | `retry.Unrecoverable`, so above example may also be shortened to: | |
246 | ||
247 | retry.Do( | |
248 | func() error { | |
249 | return retry.Unrecoverable(errors.New("special error")) | |
250 | } | |
251 | ) | |
184 | 252 | |
185 | 253 | #### type RetryIfFunc |
186 | 254 |
0 | package retry_test | |
1 | ||
2 | import ( | |
3 | "io/ioutil" | |
4 | "net/http" | |
5 | "testing" | |
6 | "time" | |
7 | ||
8 | "github.com/avast/retry-go" | |
9 | "github.com/stretchr/testify/assert" | |
10 | ) | |
11 | ||
12 | func TestCustomRetryFunction(t *testing.T) { | |
13 | url := "http://example.com" | |
14 | var body []byte | |
15 | ||
16 | err := retry.Do( | |
17 | func() error { | |
18 | resp, err := http.Get(url) | |
19 | ||
20 | if err == nil { | |
21 | defer func() { | |
22 | if err := resp.Body.Close(); err != nil { | |
23 | panic(err) | |
24 | } | |
25 | }() | |
26 | body, err = ioutil.ReadAll(resp.Body) | |
27 | } | |
28 | ||
29 | return err | |
30 | }, | |
31 | retry.DelayType(func(n uint, config *retry.Config) time.Duration { | |
32 | return 0 | |
33 | }), | |
34 | ) | |
35 | ||
36 | assert.NoError(t, err) | |
37 | assert.NotEmpty(t, body) | |
38 | } |
10 | 10 | // n = count of attempts |
11 | 11 | type OnRetryFunc func(n uint, err error) |
12 | 12 | |
13 | type config struct { | |
14 | attempts uint | |
15 | delay time.Duration | |
16 | units time.Duration | |
17 | onRetry OnRetryFunc | |
18 | retryIf RetryIfFunc | |
13 | type DelayTypeFunc func(n uint, config *Config) time.Duration | |
14 | ||
15 | type Config struct { | |
16 | attempts uint | |
17 | delay time.Duration | |
18 | onRetry OnRetryFunc | |
19 | retryIf RetryIfFunc | |
20 | delayType DelayTypeFunc | |
21 | lastErrorOnly bool | |
19 | 22 | } |
20 | 23 | |
21 | 24 | // Option represents an option for retry. |
22 | type Option func(*config) | |
25 | type Option func(*Config) | |
26 | ||
27 | // return the direct last error that came from the retried function | |
28 | // default is false (return wrapped errors with everything) | |
29 | func LastErrorOnly(lastErrorOnly bool) Option { | |
30 | return func(c *Config) { | |
31 | c.lastErrorOnly = lastErrorOnly | |
32 | } | |
33 | } | |
23 | 34 | |
24 | 35 | // Attempts set count of retry |
25 | 36 | // default is 10 |
26 | 37 | func Attempts(attempts uint) Option { |
27 | return func(c *config) { | |
38 | return func(c *Config) { | |
28 | 39 | c.attempts = attempts |
29 | 40 | } |
30 | 41 | } |
31 | 42 | |
32 | 43 | // Delay set delay between retry |
33 | // default are 1e5 units | |
44 | // default is 100ms | |
34 | 45 | func Delay(delay time.Duration) Option { |
35 | return func(c *config) { | |
46 | return func(c *Config) { | |
36 | 47 | c.delay = delay |
37 | 48 | } |
38 | 49 | } |
39 | 50 | |
40 | // Units set unit of delay (probably only for tests purpose) | |
41 | // default are microsecond | |
42 | func Units(units time.Duration) Option { | |
43 | return func(c *config) { | |
44 | c.units = units | |
51 | // DelayType set type of the delay between retries | |
52 | // default is BackOff | |
53 | func DelayType(delayType DelayTypeFunc) Option { | |
54 | return func(c *Config) { | |
55 | c.delayType = delayType | |
45 | 56 | } |
57 | } | |
58 | ||
59 | // BackOffDelay is a DelayType which increases delay between consecutive retries | |
60 | func BackOffDelay(n uint, config *Config) time.Duration { | |
61 | return config.delay * (1 << n) | |
62 | } | |
63 | ||
64 | // FixedDelay is a DelayType which keeps delay the same through all iterations | |
65 | func FixedDelay(_ uint, config *Config) time.Duration { | |
66 | return config.delay | |
46 | 67 | } |
47 | 68 | |
48 | 69 | // OnRetry function callback are called each retry |
53 | 74 | // func() error { |
54 | 75 | // return errors.New("some error") |
55 | 76 | // }, |
56 | // retry.OnRetry(func(n unit, err error) { | |
77 | // retry.OnRetry(func(n uint, err error) { | |
57 | 78 | // log.Printf("#%d: %s\n", n, err) |
58 | 79 | // }), |
59 | 80 | // ) |
60 | 81 | func OnRetry(onRetry OnRetryFunc) Option { |
61 | return func(c *config) { | |
82 | return func(c *Config) { | |
62 | 83 | c.onRetry = onRetry |
63 | 84 | } |
64 | 85 | } |
79 | 100 | // return true |
80 | 101 | // }) |
81 | 102 | // ) |
103 | // | |
104 | // By default RetryIf stops execution if the error is wrapped using `retry.Unrecoverable`, | |
105 | // so above example may also be shortened to: | |
106 | // | |
107 | // retry.Do( | |
108 | // func() error { | |
109 | // return retry.Unrecoverable(errors.New("special error")) | |
110 | // } | |
111 | // ) | |
82 | 112 | func RetryIf(retryIf RetryIfFunc) Option { |
83 | return func(c *config) { | |
113 | return func(c *Config) { | |
84 | 114 | c.retryIf = retryIf |
85 | 115 | } |
86 | 116 | } |
44 | 44 | |
45 | 45 | BREAKING CHANGES |
46 | 46 | |
47 | 1.0.2 -> 2.0.0 | |
48 | ||
49 | * argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore) | |
50 | ||
51 | * function `retry.Units` are removed | |
52 | ||
53 | * [more about this breaking change](https://github.com/avast/retry-go/issues/7) | |
54 | ||
55 | ||
47 | 56 | 0.3.0 -> 1.0.0 |
48 | 57 | |
49 | 58 | * `retry.Retry` function are changed to `retry.Do` function |
67 | 76 | var n uint |
68 | 77 | |
69 | 78 | //default |
70 | config := &config{ | |
71 | attempts: 10, | |
72 | delay: 1e5, | |
73 | units: time.Microsecond, | |
74 | onRetry: func(n uint, err error) {}, | |
75 | retryIf: func(err error) bool { return true }, | |
79 | config := &Config{ | |
80 | attempts: 10, | |
81 | delay: 100 * time.Millisecond, | |
82 | onRetry: func(n uint, err error) {}, | |
83 | retryIf: IsRecoverable, | |
84 | delayType: BackOffDelay, | |
85 | lastErrorOnly: false, | |
76 | 86 | } |
77 | 87 | |
78 | 88 | //apply opts |
80 | 90 | opt(config) |
81 | 91 | } |
82 | 92 | |
83 | errorLog := make(Error, config.attempts) | |
93 | var errorLog Error | |
94 | if !config.lastErrorOnly { | |
95 | errorLog = make(Error, config.attempts) | |
96 | } else { | |
97 | errorLog = make(Error, 1) | |
98 | } | |
84 | 99 | |
100 | lastErrIndex := n | |
85 | 101 | for n < config.attempts { |
86 | 102 | err := retryableFunc() |
87 | 103 | |
88 | 104 | if err != nil { |
89 | config.onRetry(n, err) | |
90 | errorLog[n] = err | |
105 | errorLog[lastErrIndex] = unpackUnrecoverable(err) | |
91 | 106 | |
92 | 107 | if !config.retryIf(err) { |
93 | 108 | break |
94 | 109 | } |
110 | ||
111 | config.onRetry(n, err) | |
95 | 112 | |
96 | 113 | // if this is last attempt - don't wait |
97 | 114 | if n == config.attempts-1 { |
98 | 115 | break |
99 | 116 | } |
100 | 117 | |
101 | delayTime := config.delay * (1 << (n - 1)) | |
102 | time.Sleep((time.Duration)(delayTime) * config.units) | |
118 | delayTime := config.delayType(n, config) | |
119 | time.Sleep(delayTime) | |
103 | 120 | } else { |
104 | 121 | return nil |
105 | 122 | } |
106 | 123 | |
107 | 124 | n++ |
125 | if !config.lastErrorOnly { | |
126 | lastErrIndex = n | |
127 | } | |
108 | 128 | } |
109 | 129 | |
130 | if config.lastErrorOnly { | |
131 | return errorLog[lastErrIndex] | |
132 | } | |
110 | 133 | return errorLog |
111 | 134 | } |
112 | 135 | |
143 | 166 | func (e Error) WrappedErrors() []error { |
144 | 167 | return e |
145 | 168 | } |
169 | ||
170 | type unrecoverableError struct { | |
171 | error | |
172 | } | |
173 | ||
174 | // Unrecoverable wraps an error in `unrecoverableError` struct | |
175 | func Unrecoverable(err error) error { | |
176 | return unrecoverableError{err} | |
177 | } | |
178 | ||
179 | // IsRecoverable checks if error is an instance of `unrecoverableError` | |
180 | func IsRecoverable(err error) bool { | |
181 | _, isUnrecoverable := err.(unrecoverableError) | |
182 | return !isUnrecoverable | |
183 | } | |
184 | ||
185 | func unpackUnrecoverable(err error) error { | |
186 | if unrecoverable, isUnrecoverable := err.(unrecoverableError); isUnrecoverable { | |
187 | return unrecoverable.error | |
188 | } | |
189 | ||
190 | return err | |
191 | } |
3 | 3 | "errors" |
4 | 4 | "testing" |
5 | 5 | "time" |
6 | ||
7 | "fmt" | |
6 | 8 | |
7 | 9 | "github.com/stretchr/testify/assert" |
8 | 10 | ) |
12 | 14 | err := Do( |
13 | 15 | func() error { return errors.New("test") }, |
14 | 16 | OnRetry(func(n uint, err error) { retrySum += n }), |
15 | Units(time.Nanosecond), | |
17 | Delay(time.Nanosecond), | |
16 | 18 | ) |
17 | 19 | assert.Error(t, err) |
18 | 20 | |
56 | 58 | RetryIf(func(err error) bool { |
57 | 59 | return err.Error() != "special" |
58 | 60 | }), |
59 | Units(time.Nanosecond), | |
61 | Delay(time.Nanosecond), | |
60 | 62 | ) |
61 | 63 | assert.Error(t, err) |
62 | 64 | |
65 | 67 | #2: test |
66 | 68 | #3: special` |
67 | 69 | assert.Equal(t, expectedErrorFormat, err.Error(), "retry error format") |
68 | assert.Equal(t, uint(3), retryCount, "right count of retry") | |
70 | assert.Equal(t, uint(2), retryCount, "right count of retry") | |
69 | 71 | |
70 | 72 | } |
71 | 73 | |
77 | 79 | ) |
78 | 80 | dur := time.Since(start) |
79 | 81 | assert.Error(t, err) |
80 | assert.True(t, dur > 10*time.Millisecond, "3 times default retry is longer then 10ms") | |
82 | assert.True(t, dur > 300*time.Millisecond, "3 times default retry is longer then 300ms") | |
81 | 83 | } |
84 | ||
85 | func TestFixedSleep(t *testing.T) { | |
86 | start := time.Now() | |
87 | err := Do( | |
88 | func() error { return errors.New("test") }, | |
89 | Attempts(3), | |
90 | DelayType(FixedDelay), | |
91 | ) | |
92 | dur := time.Since(start) | |
93 | assert.Error(t, err) | |
94 | assert.True(t, dur < 500*time.Millisecond, "3 times default retry is shorter then 500ms") | |
95 | } | |
96 | ||
97 | func TestLastErrorOnly(t *testing.T) { | |
98 | var retrySum uint | |
99 | err := Do( | |
100 | func() error { return fmt.Errorf("%d", retrySum) }, | |
101 | OnRetry(func(n uint, err error) { retrySum += 1 }), | |
102 | Delay(time.Nanosecond), | |
103 | LastErrorOnly(true), | |
104 | ) | |
105 | assert.Error(t, err) | |
106 | assert.Equal(t, "9", err.Error()) | |
107 | } | |
108 | ||
109 | func TestUnrecoverableError(t *testing.T) { | |
110 | attempts := 0 | |
111 | expectedErr := errors.New("error") | |
112 | err := Do( | |
113 | func() error { | |
114 | attempts++ | |
115 | return Unrecoverable(expectedErr) | |
116 | }, | |
117 | Attempts(2), | |
118 | LastErrorOnly(true), | |
119 | ) | |
120 | assert.Equal(t, expectedErr, err) | |
121 | assert.Equal(t, 1, attempts, "unrecoverable error broke the loop") | |
122 | } |