Skip to content

Commit b55b1ef

Browse files
authored
Merge pull request avast#91 from craigpastro/do-with-data
Add DoWithData function
2 parents 7855000 + d090128 commit b55b1ef

File tree

5 files changed

+160
-27
lines changed

5 files changed

+160
-27
lines changed

.github/workflows/workflow.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
name: Go
22

3-
on: [push]
3+
on:
4+
push:
5+
pull_request:
6+
branches:
7+
- master
48

59
jobs:
610
golangci-lint:
711
runs-on: ubuntu-latest
812
steps:
9-
- uses: actions/setup-go@v3
13+
- uses: actions/setup-go@v4
1014
with:
11-
go-version: 1.17
15+
go-version: 1.18
1216
- uses: actions/checkout@v3
1317
- name: golangci-lint
1418
uses: golangci/golangci-lint-action@v3
@@ -27,7 +31,7 @@ jobs:
2731
steps:
2832
- uses: actions/checkout@v3
2933
- name: Setup Go ${{ matrix.go-version }}
30-
uses: actions/setup-go@v3
34+
uses: actions/setup-go@v4
3135
with:
3236
go-version: ${{ matrix.go-version }}
3337
check-latest: true

README.md

Lines changed: 43 additions & 1 deletion
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))
3843

39-
fmt.Println(body)
44+
http get with retry with data:
45+
46+
url := "http://example.com"
47+
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
@@ -383,6 +417,14 @@ type RetryableFunc func() error
383417
384418
Function signature of retryable function
385419
420+
#### type RetryableFuncWithData
421+
422+
```go
423+
type RetryableFuncWithData[T any] func() (T, error)
424+
```
425+
426+
Function signature of retryable function with data
427+
386428
#### type Timer
387429
388430
```go

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+
)

retry.go

Lines changed: 58 additions & 16 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))
33+
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+
}
2856
29-
fmt.Println(body)
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,30 +132,33 @@ 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
if config.attempts == 0 {
99-
for err := retryableFunc(); err != nil; err = retryableFunc() {
140+
for {
141+
t, err := retryableFunc()
142+
if err == nil {
143+
return t, nil
144+
}
145+
100146
if !IsRecoverable(err) {
101-
return err
147+
return emptyT, err
102148
}
103149

104150
if !config.retryIf(err) {
105-
return err
151+
return emptyT, err
106152
}
107153

108154
n++
109155
config.onRetry(n, err)
110156
select {
111157
case <-config.timer.After(delay(config, n, err)):
112158
case <-config.context.Done():
113-
return config.context.Err()
159+
return emptyT, config.context.Err()
114160
}
115161
}
116-
117-
return nil
118162
}
119163

120164
errorLog := Error{}
@@ -126,9 +170,9 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
126170

127171
shouldRetry := true
128172
for shouldRetry {
129-
err := retryableFunc()
173+
t, err := retryableFunc()
130174
if err == nil {
131-
return nil
175+
return t, nil
132176
}
133177

134178
errorLog = append(errorLog, unpackUnrecoverable(err))
@@ -156,22 +200,20 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
156200
case <-config.timer.After(delay(config, n, err)):
157201
case <-config.context.Done():
158202
if config.lastErrorOnly {
159-
return config.context.Err()
203+
return emptyT, config.context.Err()
160204
}
161-
n++
162205

163-
return append(errorLog, config.context.Err())
206+
return emptyT, append(errorLog, config.context.Err())
164207
}
165208

166209
n++
167210
shouldRetry = shouldRetry && n < config.attempts
168211
}
169212

170213
if config.lastErrorOnly {
171-
return errorLog.Unwrap()
214+
return emptyT, errorLog.Unwrap()
172215
}
173-
174-
return errorLog
216+
return emptyT, errorLog
175217
}
176218

177219
func newDefaultRetryConfig() *Config {

retry_test.go

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
)
1313

14-
func TestDoAllFailed(t *testing.T) {
14+
func TestDoWithDataAllFailed(t *testing.T) {
1515
var retrySum uint
16-
err := Do(
17-
func() error { return errors.New("test") },
16+
v, err := DoWithData(
17+
func() (int, error) { return 7, errors.New("test") },
1818
OnRetry(func(n uint, err error) { retrySum += n }),
1919
Delay(time.Nanosecond),
2020
)
2121
assert.Error(t, err)
22+
assert.Equal(t, 0, v)
2223

2324
expectedErrorFormat := `All attempts fail:
2425
#1: test
@@ -44,7 +45,19 @@ func TestDoFirstOk(t *testing.T) {
4445
)
4546
assert.NoError(t, err)
4647
assert.Equal(t, uint(0), retrySum, "no retry")
48+
}
49+
50+
func TestDoWithDataFirstOk(t *testing.T) {
51+
returnVal := 1
4752

53+
var retrySum uint
54+
val, err := DoWithData(
55+
func() (int, error) { return returnVal, nil },
56+
OnRetry(func(n uint, err error) { retrySum += n }),
57+
)
58+
assert.NoError(t, err)
59+
assert.Equal(t, returnVal, val)
60+
assert.Equal(t, uint(0), retrySum, "no retry")
4861
}
4962

5063
func TestRetryIf(t *testing.T) {
@@ -530,7 +543,7 @@ func BenchmarkDo(b *testing.B) {
530543
testError := errors.New("test error")
531544

532545
for i := 0; i < b.N; i++ {
533-
Do(
546+
_ = Do(
534547
func() error {
535548
return testError
536549
},
@@ -540,9 +553,23 @@ func BenchmarkDo(b *testing.B) {
540553
}
541554
}
542555

556+
func BenchmarkDoWithData(b *testing.B) {
557+
testError := errors.New("test error")
558+
559+
for i := 0; i < b.N; i++ {
560+
_, _ = DoWithData(
561+
func() (int, error) {
562+
return 0, testError
563+
},
564+
Attempts(10),
565+
Delay(0),
566+
)
567+
}
568+
}
569+
543570
func BenchmarkDoNoErrors(b *testing.B) {
544571
for i := 0; i < b.N; i++ {
545-
Do(
572+
_ = Do(
546573
func() error {
547574
return nil
548575
},
@@ -552,6 +579,18 @@ func BenchmarkDoNoErrors(b *testing.B) {
552579
}
553580
}
554581

582+
func BenchmarkDoWithDataNoErrors(b *testing.B) {
583+
for i := 0; i < b.N; i++ {
584+
_, _ = DoWithData(
585+
func() (int, error) {
586+
return 0, nil
587+
},
588+
Attempts(10),
589+
Delay(0),
590+
)
591+
}
592+
}
593+
555594
func TestIsRecoverable(t *testing.T) {
556595
err := errors.New("err")
557596
assert.True(t, IsRecoverable(err))

0 commit comments

Comments
 (0)