Skip to content

Commit b94b74c

Browse files
committed
Merge branch 'master' into handle-context-timeout
2 parents 9b38e2e + fdadb7c commit b94b74c

File tree

7 files changed

+212
-82
lines changed

7 files changed

+212
-82
lines changed

.github/workflows/workflow.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ jobs:
1010
golangci-lint:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/setup-go@v3
13+
- uses: actions/setup-go@v4
1414
with:
15-
go-version: 1.17
15+
go-version: 1.18
1616
- uses: actions/checkout@v3
1717
- name: golangci-lint
1818
uses: golangci/golangci-lint-action@v3
@@ -23,15 +23,15 @@ jobs:
2323
strategy:
2424
fail-fast: false
2525
matrix:
26-
go-version: [ '1.16', '1.17', '1.18', '1.19' ]
26+
go-version: ['1.18', '1.19', '1.20']
2727
os: [ubuntu-latest, macos-latest, windows-latest]
2828
env:
2929
OS: ${{ matrix.os }}
3030
GOVERSION: ${{ matrix.go-version }}
3131
steps:
3232
- uses: actions/checkout@v3
3333
- name: Setup Go ${{ matrix.go-version }}
34-
uses: actions/setup-go@v3
34+
uses: actions/setup-go@v4
3535
with:
3636
go-version: ${{ matrix.go-version }}
3737
check-latest: true

README.md

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,36 @@ http get with retry:
3535
return nil
3636
},
3737
)
38+
if err != nil {
39+
// handle error
40+
}
41+
42+
fmt.Println(string(body))
43+
44+
http get with retry with data:
45+
46+
url := "http://example.com"
3847

39-
fmt.Println(body)
48+
body, err := retry.DoWithData(
49+
func() ([]byte, error) {
50+
resp, err := http.Get(url)
51+
if err != nil {
52+
return nil, err
53+
}
54+
defer resp.Body.Close()
55+
body, err := ioutil.ReadAll(resp.Body)
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
return body, nil
61+
},
62+
)
63+
if err != nil {
64+
// handle error
65+
}
66+
67+
fmt.Println(string(body))
4068

4169
[next examples](https://github.com/avast/retry-go/tree/master/examples)
4270

@@ -94,6 +122,12 @@ BackOffDelay is a DelayType which increases delay between consecutive retries
94122
func Do(retryableFunc RetryableFunc, opts ...Option) error
95123
```
96124

125+
#### func DoWithData
126+
127+
```go
128+
func DoWithData[T any](retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error)
129+
```
130+
97131
#### func FixedDelay
98132

99133
```go
@@ -355,19 +389,17 @@ wait for a set duration for retries.
355389
356390
example of augmenting time.After with a print statement
357391
358-
type struct MyTimer {}
392+
type struct MyTimer {}
359393
360394
func (t *MyTimer) After(d time.Duration) <- chan time.Time {
361395
fmt.Print("Timer called!")
362396
return time.After(d)
363397
}
364398
365-
retry.Do(
366-
399+
retry.Do(
367400
func() error { ... },
368401
retry.WithTimer(&MyTimer{})
369-
370-
)
402+
)
371403
372404
#### func WrapContextErrorWithLastError
373405
@@ -409,6 +441,14 @@ type RetryableFunc func() error
409441
410442
Function signature of retryable function
411443
444+
#### type RetryableFuncWithData
445+
446+
```go
447+
type RetryableFuncWithData[T any] func() (T, error)
448+
```
449+
450+
Function signature of retryable function with data
451+
412452
#### type Timer
413453
414454
```go

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.3.3
1+
4.4.0

go.mod

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
module github.com/avast/retry-go/v4
22

3-
go 1.13
3+
go 1.18
44

55
require github.com/stretchr/testify v1.8.2
6+
7+
require (
8+
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/pmezard/go-difflib v1.0.0 // indirect
10+
gopkg.in/yaml.v3 v3.0.1 // indirect
11+
)

options.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,19 +233,17 @@ func Context(ctx context.Context) Option {
233233
//
234234
// example of augmenting time.After with a print statement
235235
//
236-
// type struct MyTimer {}
236+
// type struct MyTimer {}
237237
//
238238
// func (t *MyTimer) After(d time.Duration) <- chan time.Time {
239239
// fmt.Print("Timer called!")
240240
// return time.After(d)
241241
// }
242242
//
243-
// retry.Do(
244-
//
243+
// retry.Do(
245244
// func() error { ... },
246245
// retry.WithTimer(&MyTimer{})
247-
//
248-
// )
246+
// )
249247
func WithTimer(t Timer) Option {
250248
return func(c *Config) {
251249
c.timer = t

retry.go

Lines changed: 83 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,36 @@ http get with retry:
2525
return nil
2626
},
2727
)
28+
if err != nil {
29+
// handle error
30+
}
31+
32+
fmt.Println(string(body))
2833
29-
fmt.Println(body)
34+
http get with retry with data:
35+
36+
url := "http://example.com"
37+
38+
body, err := retry.DoWithData(
39+
func() ([]byte, error) {
40+
resp, err := http.Get(url)
41+
if err != nil {
42+
return nil, err
43+
}
44+
defer resp.Body.Close()
45+
body, err := ioutil.ReadAll(resp.Body)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
return body, nil
51+
},
52+
)
53+
if err != nil {
54+
// handle error
55+
}
56+
57+
fmt.Println(string(body))
3058
3159
[next examples](https://github.com/avast/retry-go/tree/master/examples)
3260
@@ -72,6 +100,9 @@ import (
72100
// Function signature of retryable function
73101
type RetryableFunc func() error
74102

103+
// Function signature of retryable function with data
104+
type RetryableFuncWithData[T any] func() (T, error)
105+
75106
// Default timer is a wrapper around time.After
76107
type timerImpl struct{}
77108

@@ -80,7 +111,17 @@ func (t *timerImpl) After(d time.Duration) <-chan time.Time {
80111
}
81112

82113
func Do(retryableFunc RetryableFunc, opts ...Option) error {
114+
retryableFuncWithData := func() (any, error) {
115+
return nil, retryableFunc()
116+
}
117+
118+
_, err := DoWithData(retryableFuncWithData, opts...)
119+
return err
120+
}
121+
122+
func DoWithData[T any](retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error) {
83123
var n uint
124+
var emptyT T
84125

85126
// default
86127
config := newDefaultRetryConfig()
@@ -91,19 +132,24 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
91132
}
92133

93134
if err := config.context.Err(); err != nil {
94-
return err
135+
return emptyT, err
95136
}
96137

97138
// Setting attempts to 0 means we'll retry until we succeed
98139
var lastErr error
99140
if config.attempts == 0 {
100-
for err := retryableFunc(); err != nil; err = retryableFunc() {
141+
for {
142+
t, err := retryableFunc()
143+
if err == nil {
144+
return t, nil
145+
}
146+
101147
if !IsRecoverable(err) {
102-
return err
148+
return emptyT, err
103149
}
104150

105151
if !config.retryIf(err) {
106-
return err
152+
return emptyT, err
107153
}
108154

109155
lastErr = err
@@ -114,81 +160,66 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
114160
case <-config.timer.After(delay(config, n, err)):
115161
case <-config.context.Done():
116162
if config.wrapContextErrorWithLastError {
117-
return Error{config.context.Err(), lastErr}
163+
return emptyT, Error{config.context.Err(), lastErr}
118164
}
119-
return config.context.Err()
165+
return emptyT, config.context.Err()
120166
}
121167
}
122-
123-
return nil
124168
}
125169

126-
var errorLog Error
127-
if !config.lastErrorOnly {
128-
errorLog = make(Error, config.attempts)
129-
} else {
130-
errorLog = make(Error, 1)
131-
}
170+
errorLog := Error{}
132171

133172
attemptsForError := make(map[error]uint, len(config.attemptsForError))
134173
for err, attempts := range config.attemptsForError {
135174
attemptsForError[err] = attempts
136175
}
137176

138-
lastErrIndex := n
139177
shouldRetry := true
140178
for shouldRetry {
141-
err := retryableFunc()
179+
t, err := retryableFunc()
180+
if err == nil {
181+
return t, nil
182+
}
142183

143-
if err != nil {
144-
errorLog[lastErrIndex] = unpackUnrecoverable(err)
184+
errorLog = append(errorLog, unpackUnrecoverable(err))
145185

146-
if !config.retryIf(err) {
147-
break
148-
}
186+
if !config.retryIf(err) {
187+
break
188+
}
149189

150-
config.onRetry(n, err)
190+
config.onRetry(n, err)
151191

152-
for errToCheck, attempts := range attemptsForError {
153-
if errors.Is(err, errToCheck) {
154-
attempts--
155-
attemptsForError[errToCheck] = attempts
156-
shouldRetry = shouldRetry && attempts > 0
157-
}
192+
for errToCheck, attempts := range attemptsForError {
193+
if errors.Is(err, errToCheck) {
194+
attempts--
195+
attemptsForError[errToCheck] = attempts
196+
shouldRetry = shouldRetry && attempts > 0
158197
}
198+
}
159199

160-
// if this is last attempt - don't wait
161-
if n == config.attempts-1 {
162-
break
163-
}
200+
// if this is last attempt - don't wait
201+
if n == config.attempts-1 {
202+
break
203+
}
164204

165-
select {
166-
case <-config.timer.After(delay(config, n, err)):
167-
case <-config.context.Done():
168-
if config.lastErrorOnly {
169-
return config.context.Err()
170-
}
171-
n++
172-
errorLog[n] = config.context.Err()
173-
return errorLog[:lenWithoutNil(errorLog)]
205+
select {
206+
case <-config.timer.After(delay(config, n, err)):
207+
case <-config.context.Done():
208+
if config.lastErrorOnly {
209+
return emptyT, config.context.Err()
174210
}
175211

176-
} else {
177-
return nil
212+
return emptyT, append(errorLog, config.context.Err())
178213
}
179214

180215
n++
181216
shouldRetry = shouldRetry && n < config.attempts
182-
183-
if !config.lastErrorOnly {
184-
lastErrIndex = n
185-
}
186217
}
187218

188219
if config.lastErrorOnly {
189-
return errorLog[lastErrIndex]
220+
return emptyT, errorLog.Unwrap()
190221
}
191-
return errorLog[:lenWithoutNil(errorLog)]
222+
return emptyT, errorLog
192223
}
193224

194225
func newDefaultRetryConfig() *Config {
@@ -212,7 +243,7 @@ type Error []error
212243
// Error method return string representation of Error
213244
// It is an implementation of error interface
214245
func (e Error) Error() string {
215-
logWithNumber := make([]string, lenWithoutNil(e))
246+
logWithNumber := make([]string, len(e))
216247
for i, l := range e {
217248
if l != nil {
218249
logWithNumber[i] = fmt.Sprintf("#%d: %s", i+1, l.Error())
@@ -256,17 +287,7 @@ When you need to unwrap all errors, you should use `WrappedErrors()` instead.
256287
Added in version 4.2.0.
257288
*/
258289
func (e Error) Unwrap() error {
259-
return e[lenWithoutNil(e)-1]
260-
}
261-
262-
func lenWithoutNil(e Error) (count int) {
263-
for _, v := range e {
264-
if v != nil {
265-
count++
266-
}
267-
}
268-
269-
return
290+
return e[len(e)-1]
270291
}
271292

272293
// WrappedErrors returns the list of errors that this Error is wrapping.

0 commit comments

Comments
 (0)