retry

package module
v0.0.0-...-9183536 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2021 License: MIT Imports: 5 Imported by: 1

README

Retry

Retry attempts to automatically invoke the failed operation. This is useful when the error is temporary (transient network failure).

Install

go get github.com/go-camp/retry

Documentation

Index

Examples

Constants

View Source
const (
	ExpInitial    = 500 * time.Millisecond
	ExpMultiplier = 1.5
)

Default values for ExpDelayer.

Variables

View Source
var DefaultDelayer = ExpDelayer{Rand: 50}

DefaultDelayer is an ExpDelayer with Rand 50.

Functions

func Break

func Break(err error) error

Break wraps a non-nil error into BreakError.

Types

type Attempt

type Attempt struct {
	Delay          time.Duration
	ContextError   error
	OperationError error
}

func (Attempt) Err

func (a Attempt) Err() error

type BreakError

type BreakError struct {
	Err error
}

BreakError indicates that the operation should not be retried.

func (*BreakError) Error

func (e *BreakError) Error() string

func (*BreakError) Is

func (e *BreakError) Is(target error) bool

func (*BreakError) Unwrap

func (e *BreakError) Unwrap() error

type ConstantDelayer

type ConstantDelayer struct {
	// Duration is the constant delay.
	Duration time.Duration
}

ConstantDelayer provides constant delay between attempts.

Example
package main

import (
	"fmt"
	"time"

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

func main() {
	delayer := retry.ConstantDelayer{Duration: time.Second}
	for attempt := 1; attempt <= 3; attempt++ {
		delay := delayer.Delay(attempt)
		fmt.Println(delay)
	}
}
Output:

1s
1s
1s

func (ConstantDelayer) Delay

func (d ConstantDelayer) Delay(attempt int) time.Duration

Delay returns a constant delay.

type Delayer

type Delayer interface {
	Delay(attempt int) time.Duration
}

Delayer calculates the delay before the next attempt after this attempt.

The delay before the first attempt(<=0) is always 0.

type ExpDelayer

type ExpDelayer struct {
	// Initial is the delay after first attempt.
	// If Initial less than or equals to 0, the ExpInitial will be used.
	Initial time.Duration
	// Multiplier is a multiplicator used to calculate the delay after the last attempt.
	// If Multiplier less than 1, the ExpMultiplier will be used.
	Multiplier float64
	// Max is max day between attempts.
	// If Max less than or equals to 0, the math.MaxInt64 will be used.
	Max time.Duration
	// Rand gives delay a variation of ±(Rand%).
	// The range of Rand is [0,100].
	// If Rand greater than 100, 100 will be used.
	// If Rand greater than 0, the delay may greater than Max.
	Rand uint8
}

ExpDelayer provides exponential and random growth delay between attempts.

Example
package main

import (
	"fmt"
	"time"

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

func main() {
	delayer := retry.ExpDelayer{
		Initial:    1 * time.Second,
		Multiplier: 2,
		Max:        20 * time.Second,
	}
	for attempt := 1; attempt <= 10; attempt++ {
		delay := delayer.Delay(attempt)
		fmt.Println(delay)
	}
}
Output:

1s
2s
4s
8s
16s
20s
20s
20s
20s
20s
Example (Rand)
package main

import (
	"fmt"
	"time"

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

func main() {
	delayer := retry.ExpDelayer{
		Initial:    time.Second,
		Multiplier: 2,
		Max:        20 * time.Second,
		Rand:       50,
	}
	delayRanges := [][2]time.Duration{
		{500 * time.Millisecond, 1500 * time.Millisecond},
		{1 * time.Second, 3 * time.Second},
		{2 * time.Second, 6 * time.Second},
		{4 * time.Second, 12 * time.Second},
		{8 * time.Second, 24 * time.Second},
		{10 * time.Second, 30 * time.Second},
		{10 * time.Second, 30 * time.Second},
		{10 * time.Second, 30 * time.Second},
		{10 * time.Second, 30 * time.Second},
		{10 * time.Second, 30 * time.Second},
	}
	for i, delayRange := range delayRanges {
		attempt := i + 1
		delay := delayer.Delay(attempt)
		if delay >= delayRange[0] && delay <= delayRange[1] {
			fmt.Println(delayRange)
		} else {
			fmt.Println(delay, "out range of", delayRange)
		}
	}
}
Output:

[500ms 1.5s]
[1s 3s]
[2s 6s]
[4s 12s]
[8s 24s]
[10s 30s]
[10s 30s]
[10s 30s]
[10s 30s]
[10s 30s]

func (ExpDelayer) Delay

func (d ExpDelayer) Delay(attempt int) time.Duration

type NopDelayer

type NopDelayer struct{}

NopDelayer provides zero delay between attempts.

func (NopDelayer) Delay

func (NopDelayer) Delay(int) time.Duration

Delay always returns 0.

type RetryResult

type RetryResult struct {
	MaxAttempts int
	Attempts    []Attempt
}

func (RetryResult) FinalAttemptError

func (rr RetryResult) FinalAttemptError() error

func (RetryResult) FinalOperationError

func (rr RetryResult) FinalOperationError() error

type Retryer

type Retryer struct {
	// If Delayer is nil, the DefaultDelayer will be used.
	Delayer Delayer
	// MaxAttempts is the maximum number of calls to op.
	// MaxAttempts is 0 means there is no constraint on the number of attempts.
	MaxAttempts int
}

Retryer contains basic retry logic using Delayer.

Example (MaxAttempts)
package main

import (
	"context"
	"fmt"

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

func printRetryResult(result retry.RetryResult) {
	fmt.Printf("%+v\n", result)
	fmt.Println("result.FinalOperationError:", result.FinalOperationError())
	fmt.Println("result.FinalAttemptError:", result.FinalAttemptError())
}

func main() {
	retryer := retry.Retryer{
		Delayer:     retry.NopDelayer{},
		MaxAttempts: 3,
	}
	attempt := 0
	result := retryer.Retry(
		context.Background(),
		func(context.Context) error {
			attempt++
			return fmt.Errorf("err%d", attempt)
		},
	)
	printRetryResult(result)
}
Output:

{MaxAttempts:3 Attempts:[{Delay:0s ContextError:<nil> OperationError:err1} {Delay:0s ContextError:<nil> OperationError:err2} {Delay:0s ContextError:<nil> OperationError:err3}]}
result.FinalOperationError: err3
result.FinalAttemptError: err3

func (Retryer) Retry

func (r Retryer) Retry(ctx context.Context, operation func(context.Context) error) RetryResult

Retry retries to call function operation at least once.

Example (Break)
package main

import (
	"context"
	"fmt"

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

func printRetryResult(result retry.RetryResult) {
	fmt.Printf("%+v\n", result)
	fmt.Println("result.FinalOperationError:", result.FinalOperationError())
	fmt.Println("result.FinalAttemptError:", result.FinalAttemptError())
}

func main() {
	retryer := retry.Retryer{
		Delayer:     retry.NopDelayer{},
		MaxAttempts: 3,
	}
	attempt := 0
	result := retryer.Retry(
		context.Background(),
		func(context.Context) error {
			attempt++
			return retry.Break(fmt.Errorf("err%d", attempt))
		},
	)
	printRetryResult(result)
}
Output:

{MaxAttempts:3 Attempts:[{Delay:0s ContextError:<nil> OperationError:err1}]}
result.FinalOperationError: err1
result.FinalAttemptError: err1
Example (CtxCanceled)
package main

import (
	"context"
	"fmt"

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

func printRetryResult(result retry.RetryResult) {
	fmt.Printf("%+v\n", result)
	fmt.Println("result.FinalOperationError:", result.FinalOperationError())
	fmt.Println("result.FinalAttemptError:", result.FinalAttemptError())
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	retryer := retry.Retryer{
		Delayer:     retry.NopDelayer{},
		MaxAttempts: 3,
	}
	attempt := 0
	result := retryer.Retry(
		ctx,
		func(context.Context) error {
			attempt++
			return fmt.Errorf("err%d", attempt)
		},
	)
	printRetryResult(result)
}
Output:

{MaxAttempts:3 Attempts:[{Delay:0s ContextError:<nil> OperationError:err1} {Delay:0s ContextError:context canceled OperationError:<nil>}]}
result.FinalOperationError: err1
result.FinalAttemptError: context canceled
Example (CtxCanceled2)
package main

import (
	"context"
	"fmt"
	"time"

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

func printRetryResult(result retry.RetryResult) {
	fmt.Printf("%+v\n", result)
	fmt.Println("result.FinalOperationError:", result.FinalOperationError())
	fmt.Println("result.FinalAttemptError:", result.FinalAttemptError())
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		time.Sleep(time.Millisecond)
		cancel()
	}()
	retryer := retry.Retryer{
		Delayer:     retry.ConstantDelayer{Duration: 2 * time.Millisecond},
		MaxAttempts: 3,
	}
	attempt := 0
	result := retryer.Retry(
		ctx,
		func(context.Context) error {
			attempt++
			return fmt.Errorf("err%d", attempt)
		},
	)
	printRetryResult(result)
}
Output:

{MaxAttempts:3 Attempts:[{Delay:0s ContextError:<nil> OperationError:err1} {Delay:2ms ContextError:context canceled OperationError:<nil>}]}
result.FinalOperationError: err1
result.FinalAttemptError: context canceled
Example (CtxTimeout)
package main

import (
	"context"
	"fmt"
	"time"

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

func printRetryResult(result retry.RetryResult) {
	fmt.Printf("%+v\n", result)
	fmt.Println("result.FinalOperationError:", result.FinalOperationError())
	fmt.Println("result.FinalAttemptError:", result.FinalAttemptError())
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
	defer cancel()
	retryer := retry.Retryer{
		Delayer:     retry.ConstantDelayer{Duration: 2 * time.Millisecond},
		MaxAttempts: 3,
	}
	attempt := 0
	result := retryer.Retry(
		ctx,
		func(context.Context) error {
			attempt++
			return fmt.Errorf("err%d", attempt)
		},
	)
	printRetryResult(result)
}
Output:

{MaxAttempts:3 Attempts:[{Delay:0s ContextError:<nil> OperationError:err1} {Delay:2ms ContextError:context deadline exceeded OperationError:<nil>}]}
result.FinalOperationError: err1
result.FinalAttemptError: context deadline exceeded
Example (Success)
package main

import (
	"context"
	"fmt"

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

func printRetryResult(result retry.RetryResult) {
	fmt.Printf("%+v\n", result)
	fmt.Println("result.FinalOperationError:", result.FinalOperationError())
	fmt.Println("result.FinalAttemptError:", result.FinalAttemptError())
}

func main() {
	retryer := retry.Retryer{
		Delayer:     retry.NopDelayer{},
		MaxAttempts: 3,
	}
	attempt := 0
	result := retryer.Retry(
		context.Background(),
		func(context.Context) error {
			attempt++
			if attempt > 1 {
				return nil
			}
			return fmt.Errorf("err%d", attempt)
		},
	)
	printRetryResult(result)
}
Output:

{MaxAttempts:3 Attempts:[{Delay:0s ContextError:<nil> OperationError:err1} {Delay:0s ContextError:<nil> OperationError:<nil>}]}
result.FinalOperationError: <nil>
result.FinalAttemptError: <nil>

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL