Skip to content

Commit

Permalink
feat: retry with ctx deadline (zeromicro#3626)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevwan authored Oct 15, 2023
1 parent 4f22034 commit fd070fe
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 22 deletions.
4 changes: 4 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
coverage:
status:
patch: true
project: false # disabled because project coverage is not stable
comment:
layout: "flags, files"
behavior: once
Expand Down
14 changes: 5 additions & 9 deletions core/fx/retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ package fx

import (
"context"
"errors"
"time"

"github.com/zeromicro/go-zero/core/errorx"
)

const defaultRetryTimes = 3

var errTimeout = errors.New("retry timeout")

type (
// RetryOption defines the method to customize DoWithRetry.
RetryOption func(*retryOptions)
Expand All @@ -28,7 +25,7 @@ type (
// and performs modification operations, it is best to lock them,
// otherwise there may be data race issues
func DoWithRetry(fn func() error, opts ...RetryOption) error {
return retry(func(errChan chan error, retryCount int) {
return retry(context.Background(), func(errChan chan error, retryCount int) {
errChan <- fn()
}, opts...)
}
Expand All @@ -40,20 +37,19 @@ func DoWithRetry(fn func() error, opts ...RetryOption) error {
// otherwise there may be data race issues
func DoWithRetryCtx(ctx context.Context, fn func(ctx context.Context, retryCount int) error,
opts ...RetryOption) error {
return retry(func(errChan chan error, retryCount int) {
return retry(ctx, func(errChan chan error, retryCount int) {
errChan <- fn(ctx, retryCount)
}, opts...)
}

func retry(fn func(errChan chan error, retryCount int), opts ...RetryOption) error {
func retry(ctx context.Context, fn func(errChan chan error, retryCount int), opts ...RetryOption) error {
options := newRetryOptions()
for _, opt := range opts {
opt(options)
}

var berr errorx.BatchError
var cancelFunc context.CancelFunc
ctx := context.Background()
if options.timeout > 0 {
ctx, cancelFunc = context.WithTimeout(ctx, options.timeout)
defer cancelFunc()
Expand All @@ -71,14 +67,14 @@ func retry(fn func(errChan chan error, retryCount int), opts ...RetryOption) err
return nil
}
case <-ctx.Done():
berr.Add(errTimeout)
berr.Add(ctx.Err())
return berr.Err()
}

if options.interval > 0 {
select {
case <-ctx.Done():
berr.Add(errTimeout)
berr.Add(ctx.Err())
return berr.Err()
case <-time.After(options.interval):
}
Expand Down
58 changes: 45 additions & 13 deletions core/fx/retry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,51 @@ func TestRetryWithInterval(t *testing.T) {
}

func TestRetryCtx(t *testing.T) {
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
if retryCount == 0 {
t.Run("with timeout", func(t *testing.T) {
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
if retryCount == 0 {
return errors.New("any")
}
time.Sleep(time.Millisecond * 150)
return nil
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))

assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
if retryCount == 1 {
return nil
}
time.Sleep(time.Millisecond * 150)
return errors.New("any ")
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
})

t.Run("with deadline exceeded", func(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond*250))
defer cancel()

var times int
assert.Error(t, DoWithRetryCtx(ctx, func(ctx context.Context, retryCount int) error {
times++
time.Sleep(time.Millisecond * 150)
return errors.New("any")
}
time.Sleep(time.Millisecond * 150)
return nil
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
}, WithInterval(time.Millisecond*150)))
assert.Equal(t, 1, times)
})

assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
if retryCount == 1 {
return nil
}
time.Sleep(time.Millisecond * 150)
return errors.New("any ")
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
t.Run("with deadline not exceeded", func(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond*250))
defer cancel()

var times int
assert.NoError(t, DoWithRetryCtx(ctx, func(ctx context.Context, retryCount int) error {
times++
if times == defaultRetryTimes {
return nil
}

time.Sleep(time.Millisecond * 50)
return errors.New("any")
}))
assert.Equal(t, defaultRetryTimes, times)
})
}

0 comments on commit fd070fe

Please sign in to comment.