Skip to content

Commit

Permalink
Merge pull request #26 from shogo82148/introduce-do-value
Browse files Browse the repository at this point in the history
introduce DoValue
  • Loading branch information
shogo82148 authored Dec 29, 2023
2 parents 0a0ccb6 + 875aa5d commit 3b50794
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 5 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jobs:
name: Test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go:
- "stable"
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,40 @@ func main() {
}
```

```go
package main

import (
"context"
"errors"
"fmt"
"time"

"github.com/shogo82148/go-retry"
)

type Result int

func DoSomething(ctx context.Context) (Result, error) {
// do something here that should to do exponential backoff https://en.wikipedia.org/wiki/Exponential_backoff
return 0, errors.New("fails")
}

var policy = retry.Policy{
MinDelay: 100 * time.Millisecond,
MaxDelay: time.Second,
MaxCount: 10,
}

func DoSomethingWithRetry(ctx context.Context) (Result, error) {
return retry.DoValue(ctx, policy, DoSomething)
}

func main() {
fmt.Println(DoSomethingWithRetry(context.Background()))
}
```

## PRIOR ARTS

This package is based on [lestrrat-go/backoff](https://github.com/lestrrat-go/backoff) and [Yak Shaving With Backoff Libraries in Go](https://medium.com/@lestrrat/yak-shaving-with-backoff-libraries-in-go-80240f0aa30c).
Expand Down
22 changes: 22 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,25 @@ func ExampleMarkPermanent() {
// unstable func is called!
// some error!
}

func ExampleDoValue() {
policy := &retry.Policy{
MaxCount: 3,
}

count := 0
_, err := retry.DoValue(context.Background(), policy, func() (int, error) {
count++
fmt.Printf("#%d: unstable func is called!\n", count)
return 0, errors.New("some error!")
})
if err != nil {
fmt.Println(err)
}

// Output:
// #1: unstable func is called!
// #2: unstable func is called!
// #3: unstable func is called!
// some error!
}
45 changes: 45 additions & 0 deletions func.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package retry

import (
"context"
"errors"
)

// DoValue executes f with retrying policy.
// It is a shorthand of Policy.Start and Retrier.Continue.
// If f returns an error, retry to execute f until f returns nil error.
// If the error implements interface{ Temporary() bool } and Temporary() returns false,
// DoValue doesn't retry and returns the error.
func DoValue[T any](ctx context.Context, policy *Policy, f func() (T, error)) (T, error) {
var zero T
var err error
var target *temporary

retrier := policy.Start(ctx)
for retrier.Continue() {
var v T
v, err = f()
if err == nil {
return v, nil
}

// short cut for calling isPermanent and Unwrap
if err, ok := err.(*permanentError); ok {
return zero, err.error
}

if target == nil {
// lazy allocation of target
target = new(temporary)
}
if errors.As(err, target) {
if !(*target).Temporary() {
return zero, err
}
}
}
if err := retrier.err; err != nil {
return zero, err
}
return zero, err
}
40 changes: 40 additions & 0 deletions func_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package retry

import (
"context"
"errors"
"fmt"
"testing"
)

func TestDoValue_Success(t *testing.T) {
policy := &Policy{
MaxCount: -1,
}

var count int
v, err := DoValue(context.Background(), policy, func() (int, error) {
count++
if count < 3 {
return 0, fmt.Errorf("error %d", count)
}
return 42, nil
})
if err != nil {
t.Fatal(err)
}
if v != 42 {
t.Errorf("want %d, got %d", 42, v)
}
}

func TestDoValue_MarkPermanent(t *testing.T) {
permanentErr := errors.New("permanent error")
policy := &Policy{}
_, err := DoValue(context.Background(), policy, func() (int, error) {
return 0, MarkPermanent(permanentErr)
})
if err != permanentErr {
t.Errorf("want error is %#v, got %#v", err, permanentErr)
}
}
13 changes: 8 additions & 5 deletions retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,8 @@ func (p *Policy) Start(ctx context.Context) *Retrier {
// If the error implements interface{ Temporary() bool } and Temporary() returns false,
// Do doesn't retry and returns the error.
func (p *Policy) Do(ctx context.Context, f func() error) error {
type Temporary interface {
Temporary() bool
}
var err error
var target *Temporary
var target *temporary

retrier := p.Start(ctx)
for retrier.Continue() {
Expand All @@ -80,7 +77,7 @@ func (p *Policy) Do(ctx context.Context, f func() error) error {

if target == nil {
// lazy allocation of target
target = new(Temporary)
target = new(temporary)
}
if errors.As(err, target) {
if !(*target).Temporary() {
Expand All @@ -94,6 +91,12 @@ func (p *Policy) Do(ctx context.Context, f func() error) error {
return err
}

type temporary interface {
Temporary() bool
}

var _ temporary = (*permanentError)(nil)

type permanentError struct {
error
}
Expand Down

0 comments on commit 3b50794

Please sign in to comment.