Skip to content

Commit

Permalink
Add DoValue, which requires generics and bumps to Go 1.21 (#26)
Browse files Browse the repository at this point in the history
Closes #22
  • Loading branch information
sethvargo committed Jul 30, 2024
1 parent a99f7cd commit 9bf6450
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 116 deletions.
24 changes: 15 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ name: 'Test'
on:
push:
branches:
- 'main'
- 'main'
tags:
- '*'
- '*'
pull_request:
branches:
- 'main'
- 'main'
workflow_dispatch:

concurrency:
Expand All @@ -20,11 +20,17 @@ jobs:
runs-on: 'ubuntu-latest'

steps:
- uses: 'actions/checkout@v3'
- uses: 'actions/checkout@v4'

- uses: actions/setup-go@v3
with:
go-version: '1.16'
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'

- name: 'Test'
run: 'make test'
- name: 'Test'
run: |-
go test \
-count=1 \
-race \
-short \
-timeout=5m \
./...
8 changes: 0 additions & 8 deletions Makefile

This file was deleted.

52 changes: 0 additions & 52 deletions benchmark/benchmark_test.go

This file was deleted.

10 changes: 0 additions & 10 deletions benchmark/go.mod

This file was deleted.

25 changes: 0 additions & 25 deletions benchmark/go.sum

This file was deleted.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/sethvargo/go-retry

go 1.16
go 1.21

// Something weird happened with the v0.2.0 tag where the commit in the module
// registry doesn't match the commit on GitHub.
Expand Down
Empty file removed go.sum
Empty file.
34 changes: 23 additions & 11 deletions retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import (
"time"
)

// RetryFunc is a function passed to retry.
// RetryFunc is a function passed to [Do].
type RetryFunc func(ctx context.Context) error

// RetryFuncValue is a function passed to [Do] which returns a value.
type RetryFuncValue[T any] func(ctx context.Context) (T, error)

type retryableError struct {
err error
}
Expand All @@ -46,47 +49,56 @@ func (e *retryableError) Error() string {
return "retryable: " + e.err.Error()
}

// Do wraps a function with a backoff to retry. The provided context is the same
// context passed to the RetryFunc.
func Do(ctx context.Context, b Backoff, f RetryFunc) error {
func DoValue[T any](ctx context.Context, b Backoff, f RetryFuncValue[T]) (T, error) {
var nilT T

for {
// Return immediately if ctx is canceled
select {
case <-ctx.Done():
return ctx.Err()
return nilT, ctx.Err()
default:
}

err := f(ctx)
v, err := f(ctx)
if err == nil {
return nil
return v, nil
}

// Not retryable
var rerr *retryableError
if !errors.As(err, &rerr) {
return err
return nilT, err
}

next, stop := b.Next()
if stop {
return rerr.Unwrap()
return nilT, rerr.Unwrap()
}

// ctx.Done() has priority, so we test it alone first
select {
case <-ctx.Done():
return ctx.Err()
return nilT, ctx.Err()
default:
}

t := time.NewTimer(next)
select {
case <-ctx.Done():
t.Stop()
return ctx.Err()
return nilT, ctx.Err()
case <-t.C:
continue
}
}
}

// Do wraps a function with a backoff to retry. The provided context is the same
// context passed to the [RetryFunc].
func Do(ctx context.Context, b Backoff, f RetryFunc) error {
_, err := DoValue(ctx, b, func(ctx context.Context) (*struct{}, error) {
return nil, f(ctx)
})
return err
}
52 changes: 52 additions & 0 deletions retry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ func TestRetryableError(t *testing.T) {
}
}

func TestDoValue(t *testing.T) {
t.Parallel()

t.Run("returns_value", func(t *testing.T) {
t.Parallel()

ctx := context.Background()
b := retry.WithMaxRetries(3, retry.BackoffFunc(func() (time.Duration, bool) {
return 1 * time.Nanosecond, false
}))

v, err := retry.DoValue(ctx, b, func(_ context.Context) (string, error) {
return "foo", nil
})
if err != nil {
t.Fatal("expected err")
}

if got, want := v, "foo"; got != want {
t.Errorf("expected %v to be %v", got, want)
}
})
}

func TestDo(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -175,6 +199,34 @@ func ExampleDo_customRetry() {
}
}

func ExampleDoValue() {
ctx := context.Background()

b := retry.NewFibonacci(1 * time.Nanosecond)

body, err := retry.DoValue(ctx, retry.WithMaxRetries(3, b), func(ctx context.Context) ([]byte, error) {
resp, err := http.Get("https://google.com/")
if err != nil {
return nil, err
}
defer resp.Body.Close()

switch resp.StatusCode / 100 {
case 4:
return nil, fmt.Errorf("bad response: %v", resp.StatusCode)
case 5:
return nil, retry.RetryableError(fmt.Errorf("bad response: %v", resp.StatusCode))
default:
b, _ := io.ReadAll(resp.Body)
return b, nil
}
})
if err != nil {
// handle error
}
_ = body
}

func TestCancel(t *testing.T) {
for i := 0; i < 100000; i++ {
ctx, cancel := context.WithCancel(context.Background())
Expand Down

0 comments on commit 9bf6450

Please sign in to comment.