mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-12 08:42:29 +08:00
Add Retry backoff policy (#173)
The aim of this policy is to enable the configuration of various types of backoff mathematical curves. Should this modification be deemed suitable, I will proceed to implement an exponential curve backoff policy and set of custom backoff policy Warning: It's major break
This commit is contained in:
@@ -18,14 +18,14 @@ const (
|
|||||||
// DefaultRetryTimes times of retry
|
// DefaultRetryTimes times of retry
|
||||||
DefaultRetryTimes = 5
|
DefaultRetryTimes = 5
|
||||||
// DefaultRetryDuration time duration of two retries
|
// DefaultRetryDuration time duration of two retries
|
||||||
DefaultRetryDuration = time.Second * 3
|
DefaultRetryLinearInterval = time.Second * 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// RetryConfig is config for retry
|
// RetryConfig is config for retry
|
||||||
type RetryConfig struct {
|
type RetryConfig struct {
|
||||||
context context.Context
|
context context.Context
|
||||||
retryTimes uint
|
retryTimes uint
|
||||||
retryDuration time.Duration
|
backoffStrategy BackoffStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetryFunc is function that retry executes
|
// RetryFunc is function that retry executes
|
||||||
@@ -42,11 +42,17 @@ func RetryTimes(n uint) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetryDuration set duration of retries.
|
// RetryWithLinearBackoff set linear strategy backoff
|
||||||
// Play: https://go.dev/play/p/nk2XRmagfVF
|
// todo: Add playground link
|
||||||
func RetryDuration(d time.Duration) Option {
|
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) {
|
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
|
// Play: https://go.dev/play/p/nk2XRmagfVF
|
||||||
func Retry(retryFunc RetryFunc, opts ...Option) error {
|
func Retry(retryFunc RetryFunc, opts ...Option) error {
|
||||||
config := &RetryConfig{
|
config := &RetryConfig{
|
||||||
retryTimes: DefaultRetryTimes,
|
retryTimes: DefaultRetryTimes,
|
||||||
retryDuration: DefaultRetryDuration,
|
context: context.TODO(),
|
||||||
context: context.TODO(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(config)
|
opt(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.backoffStrategy == nil {
|
||||||
|
config.backoffStrategy = &linear{
|
||||||
|
interval: DefaultRetryLinearInterval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var i uint
|
var i uint
|
||||||
for i < config.retryTimes {
|
for i < config.retryTimes {
|
||||||
err := retryFunc()
|
err := retryFunc()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
select {
|
select {
|
||||||
case <-time.After(config.retryDuration):
|
case <-time.After(config.backoffStrategy.CalculateInterval()):
|
||||||
case <-config.context.Done():
|
case <-config.context.Done():
|
||||||
return errors.New("retry is cancelled")
|
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)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func ExampleContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Retry(increaseNumber,
|
Retry(increaseNumber,
|
||||||
RetryDuration(time.Microsecond*50),
|
RetryWithLinearBackoff(time.Microsecond*50),
|
||||||
Context(ctx),
|
Context(ctx),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ func ExampleContext() {
|
|||||||
// 4
|
// 4
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleRetryDuration() {
|
func ExampleRetryWithLinearBackoff() {
|
||||||
number := 0
|
number := 0
|
||||||
increaseNumber := func() error {
|
increaseNumber := func() error {
|
||||||
number++
|
number++
|
||||||
@@ -40,7 +40,7 @@ func ExampleRetryDuration() {
|
|||||||
return errors.New("error occurs")
|
return errors.New("error occurs")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50))
|
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ func ExampleRetry() {
|
|||||||
return errors.New("error occurs")
|
return errors.New("error occurs")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50))
|
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestRetryFailed(t *testing.T) {
|
|||||||
return errors.New("error occurs")
|
return errors.New("error occurs")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50))
|
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
|
||||||
|
|
||||||
assert.IsNotNil(err)
|
assert.IsNotNil(err)
|
||||||
assert.Equal(DefaultRetryTimes, number)
|
assert.Equal(DefaultRetryTimes, number)
|
||||||
@@ -40,7 +40,7 @@ func TestRetrySucceeded(t *testing.T) {
|
|||||||
return errors.New("error occurs")
|
return errors.New("error occurs")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50))
|
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
|
||||||
|
|
||||||
assert.IsNil(err)
|
assert.IsNil(err)
|
||||||
assert.Equal(DefaultRetryTimes, number)
|
assert.Equal(DefaultRetryTimes, number)
|
||||||
@@ -57,7 +57,7 @@ func TestSetRetryTimes(t *testing.T) {
|
|||||||
return errors.New("error occurs")
|
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.IsNotNil(err)
|
||||||
assert.Equal(3, number)
|
assert.Equal(3, number)
|
||||||
@@ -79,7 +79,7 @@ func TestCancelRetry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := Retry(increaseNumber,
|
err := Retry(increaseNumber,
|
||||||
RetryDuration(time.Microsecond*50),
|
RetryWithLinearBackoff(time.Microsecond*50),
|
||||||
Context(ctx),
|
Context(ctx),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user