diff --git a/retry/retry.go b/retry/retry.go index 460883b..8d0cf64 100644 --- a/retry/retry.go +++ b/retry/retry.go @@ -18,14 +18,14 @@ const ( // DefaultRetryTimes times of retry DefaultRetryTimes = 5 // DefaultRetryDuration time duration of two retries - DefaultRetryDuration = time.Second * 3 + DefaultRetryLinearInterval = time.Second * 3 ) // RetryConfig is config for retry type RetryConfig struct { - context context.Context - retryTimes uint - retryDuration time.Duration + context context.Context + retryTimes uint + backoffStrategy BackoffStrategy } // RetryFunc is function that retry executes @@ -42,11 +42,17 @@ func RetryTimes(n uint) Option { } } -// RetryDuration set duration of retries. -// Play: https://go.dev/play/p/nk2XRmagfVF -func RetryDuration(d time.Duration) Option { +// RetryWithLinearBackoff set linear strategy backoff +// todo: Add playground link +func RetryWithLinearBackoff(interval time.Duration) Option { + if interval <= 0 { + panic("programming error: retry interval should not be lower or equal to 0") + } + return func(rc *RetryConfig) { - rc.retryDuration = d + rc.backoffStrategy = &linear{ + interval: interval, + } } } @@ -63,21 +69,26 @@ func Context(ctx context.Context) Option { // Play: https://go.dev/play/p/nk2XRmagfVF func Retry(retryFunc RetryFunc, opts ...Option) error { config := &RetryConfig{ - retryTimes: DefaultRetryTimes, - retryDuration: DefaultRetryDuration, - context: context.TODO(), + retryTimes: DefaultRetryTimes, + context: context.TODO(), } for _, opt := range opts { opt(config) } + if config.backoffStrategy == nil { + config.backoffStrategy = &linear{ + interval: DefaultRetryLinearInterval, + } + } + var i uint for i < config.retryTimes { err := retryFunc() if err != nil { select { - case <-time.After(config.retryDuration): + case <-time.After(config.backoffStrategy.CalculateInterval()): case <-config.context.Done(): return errors.New("retry is cancelled") } @@ -93,3 +104,21 @@ func Retry(retryFunc RetryFunc, opts ...Option) error { return fmt.Errorf("function %s run failed after %d times retry", funcName, i) } + +// BackoffStrategy is an interface that defines a method for calculating backoff intervals. +type BackoffStrategy interface { + // CalculateInterval returns the time.Duration after which the next retry attempt should be made. + CalculateInterval() time.Duration +} + +// linear is a struct that implements the BackoffStrategy interface using a linear backoff strategy. +type linear struct { + // interval specifies the fixed duration to wait between retry attempts. + interval time.Duration +} + +// CalculateInterval is the method implementation for the linear struct. +// It returns the fixed interval defined in the linear struct. +func (l *linear) CalculateInterval() time.Duration { + return l.interval +} diff --git a/retry/retry_example_test.go b/retry/retry_example_test.go index ac1d003..431afc4 100644 --- a/retry/retry_example_test.go +++ b/retry/retry_example_test.go @@ -20,7 +20,7 @@ func ExampleContext() { } Retry(increaseNumber, - RetryDuration(time.Microsecond*50), + RetryWithLinearBackoff(time.Microsecond*50), Context(ctx), ) @@ -30,7 +30,7 @@ func ExampleContext() { // 4 } -func ExampleRetryDuration() { +func ExampleRetryWithLinearBackoff() { number := 0 increaseNumber := func() error { number++ @@ -40,7 +40,7 @@ func ExampleRetryDuration() { return errors.New("error occurs") } - err := Retry(increaseNumber, RetryDuration(time.Microsecond*50)) + err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50)) if err != nil { return } @@ -81,7 +81,7 @@ func ExampleRetry() { return errors.New("error occurs") } - err := Retry(increaseNumber, RetryDuration(time.Microsecond*50)) + err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50)) if err != nil { return } diff --git a/retry/retry_test.go b/retry/retry_test.go index 7256aa6..373c821 100644 --- a/retry/retry_test.go +++ b/retry/retry_test.go @@ -20,7 +20,7 @@ func TestRetryFailed(t *testing.T) { return errors.New("error occurs") } - err := Retry(increaseNumber, RetryDuration(time.Microsecond*50)) + err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50)) assert.IsNotNil(err) assert.Equal(DefaultRetryTimes, number) @@ -40,7 +40,7 @@ func TestRetrySucceeded(t *testing.T) { return errors.New("error occurs") } - err := Retry(increaseNumber, RetryDuration(time.Microsecond*50)) + err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50)) assert.IsNil(err) assert.Equal(DefaultRetryTimes, number) @@ -57,7 +57,7 @@ func TestSetRetryTimes(t *testing.T) { return errors.New("error occurs") } - err := Retry(increaseNumber, RetryDuration(time.Microsecond*50), RetryTimes(3)) + err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50), RetryTimes(3)) assert.IsNotNil(err) assert.Equal(3, number) @@ -79,7 +79,7 @@ func TestCancelRetry(t *testing.T) { } err := Retry(increaseNumber, - RetryDuration(time.Microsecond*50), + RetryWithLinearBackoff(time.Microsecond*50), Context(ctx), )