retry

package module
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Mar 14, 2025 License: Apache-2.0 Imports: 4 Imported by: 1

README

retry

retry provides simple task retry logic.

Build Status codecov.io Go Report Card Apache V2 License Quality Gate Status GitHub release PkgGoDev

Summary

retry provides simple ways of executing tasks with configurable retry semantics. A focus is place on external configuration driving the retry behavior. Tasks may be executed without retries, with a constant interval between retries, or using an exponential backoff.

Table of Contents

Code of Conduct

This project and everyone participating in it are governed by the XMiDT Code Of Conduct. By participating, you agree to this Code.

Install

go get -u github.com/xmidt-org/retry

Contributing

Refer to CONTRIBUTING.md.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddSuccessFail

func AddSuccessFail[V any, T SimpleTask](success, fail V, raw T) func(context.Context) (V, error)

AddSuccessFail wraps a SimpleTask so that it returns a value based on whether an error is encountered. This function is useful when invoking functions from other packages that only return errors.

The returned task closure may be passed to a Runner[V].

func AddValue

func AddValue[V any, T SimpleTask](result V, raw T) func(context.Context) (V, error)

AddValue wraps a SimpleTask so that it always returns the given result, regardless of any error.

The returned task closure may be passed to a Runner[V].

func AddZero

func AddZero[V any, T SimpleTask](raw T) func(context.Context) (V, error)

AddZero wraps a SimpleTask so that it always returns the zero value of some arbitrary type V, regardless of any error.

The returned task closure may be passed to a Runner[V].

func DefaultTestErrorForRetry added in v0.0.3

func DefaultTestErrorForRetry(err error) bool

DefaultTestErrorForRetry is the default strategy for determining whether a retry should occur. This function does not consider the value result from a task.

This function applies the following logic:

- if err == nil, the task is assumed to be a success and this function returns false (no more retries) - if err implements ShouldRetryable, then err.ShouldRetry() is returned - if err supplies a Temporary() bool method, then err.Temporary() is returned - failing other logic, this function returns true

func SetRetryable

func SetRetryable(err error, retryable bool) error

SetRetryable associates retryability with a given error. The returned error implements ShouldRetryable, returning the given value, and provides an Unwrap method for the original error.

Types

type Attempt

type Attempt[V any] struct {
	// Context is the policy context that spans the task attempts.
	// This field will never be nil.
	Context context.Context

	// Result is the value returned by the task attempt.
	Result V

	// Err is the error returned by the task.  If nil, this attempt
	// represents a success.
	Err error

	// Retries indicates the number of retries so far.  This field will
	// be zero (0) on the initial attempt.
	Retries int

	// If another retry will be attempted, this is the duration that the
	// runner will wait before the next retry.  If this field is zero (0),
	// then no further retries will be attempted.
	//
	// Use Done() to determine if this is the last attempt.  This isolates
	// client code from future changes.
	Next time.Duration
}

Attempt represents the result of trying to invoke a task, including a success.

func (Attempt[V]) Done

func (a Attempt[V]) Done() bool

Done returns true if this represents the last attempt to execute the task. A successful task attempt also returns true from this method, as there will be no more attempts.

type Config

type Config struct {
	// Interval specifies the retry interval for a constant backoff and the
	// initial, starting interval for an exponential backoff.
	//
	// If this field is unset, no retries will happen.
	Interval time.Duration `json:"interval" yaml:"interval"`

	// Jitter is the random jitter for an exponential backoff.
	//
	// If this value is nonpositive, it is ignored.  If both this field and
	// Multiplier are unset, the resulting policy will be a constant backoff.
	Jitter float64 `json:"jitter" yaml:"jitter"`

	// Multiplier is the interval multiplier for an exponential backoff.
	//
	// If this value is less than or equal to 1.0, it is ignored.  If both this field and
	// Jitter are unset, the resulting policy will be a constant backoff.
	Multiplier float64 `json:"multiplier" yaml:"multiplier"`

	// MaxRetries is the absolute maximum number of retries performed, regardless
	// of other fields.  If this field is nonpositive, operations are retried until
	// they succeed.
	MaxRetries int `json:"maxRetries" yaml:"maxRetries"`

	// MaxElapsedTime is the absolute amount of time an operation and its retries are
	// allowed to take before giving up.  If this field is nonpositive, no maximum
	// elapsed time is enforced.
	MaxElapsedTime time.Duration `json:"maxElapsedTime" yaml:"maxElapsedTime"`

	// MaxInterval is the upper limit for each retry interval for an exponential backoff.
	// If Jitter and Multiplier are unset, or if this value is smaller than Interval, then
	// this field is ignored.
	MaxInterval time.Duration `json:"maxInterval" yaml:"maxInterval"`
}

Config represents the possible options when creating a Policy. This type is friendly to being unmarshaled from external sources.

Three basic kinds of retry policies are created by this type:

  • if Interval is nonpositive, the created policy will never retry anything
  • if Interval is positive but Jitter and Multiplier are not, the created policy will return a constant, unchanging retry interval
  • if Interval is positive and Jitter or Multiplier are as well, the created policy will return an exponentially increasing retry interval

func (Config) NewPolicy

func (c Config) NewPolicy(parentCtx context.Context) Policy

NewPolicy implements PolicyFactory and uses this configuration to create the type of retry policy indicated by the Interval, Jitter, and Multiplier fields.

type OnAttempt

type OnAttempt[V any] func(Attempt[V])

OnAttempt is an optional task callback that is invoked after each attempt at invoking the task, including a successful one.

This function must not panic or block, or task retries will be impacted.

type Policy

type Policy interface {
	// Context returns the context associated with this policy.  This method never returns nil.
	Context() context.Context

	// Cancel halts all future retries and cleans up resources associated with this policy.
	// This method is idempotent.  After it is called the first time, Next always returns (0, false).
	Cancel()

	// Next obtains the retry interval to use next.  Typically, a caller will
	// sleep for the returned duration before trying the operation again.
	//
	// This method is not safe for concurrent invocation.
	//
	// If this method returns true, the duration will be a positive value indicating
	// the amount of time that should elapse before the next retry.
	//
	// If this method returns false, the duration will always be zero (0).
	Next() (time.Duration, bool)
}

Policy is a retry algorithm. Policies are not safe for concurrent use. A Policy should be created each time an operation is to be executed.

type PolicyFactory

type PolicyFactory interface {
	// NewPolicy creates the retry Policy this factory is configured to make.
	// This method should incorporate the context into the returned Policy.
	// Typically, this will mean a child context with a cancelation function.
	NewPolicy(context.Context) Policy
}

PolicyFactory is a strategy for creating retry policies. The Config type is an example of an implementation of this interface that can be unmarshaled from an external source.

type PolicyFactoryFunc

type PolicyFactoryFunc func(context.Context) Policy

PolicyFactoryFunc is a function type that implements PolicyFactory.

func (PolicyFactoryFunc) NewPolicy

func (pff PolicyFactoryFunc) NewPolicy(ctx context.Context) Policy

type RunnableTask

type RunnableTask[V any] interface {
	~func() (V, error) | ~func(context.Context) (V, error)
}

RunnableTask is the underlying type of closure that implements a task. A RunnableTask may or may not accept a context, but will always return a result and an error.

type Runner

type Runner[V any] interface {
	// Run executes a task at least once, retrying failures according to
	// the configured PolicyFactory.  If all attempts fail, this method returns
	// the error along with the zero value for V.  Otherwise, the value V that
	// resulted from the final, successful attempt is returned along with a nil error.
	//
	// The context passed to this method must never be nil.  Use context.Background()
	// or context.TODO() as appropriate rather than nil.
	//
	// The configured PolicyFactory may impose a time limit, e.g. the Config.MaxElapsedTime
	// field.  In this case, if the time limit is reached, task attempts will halt regardless
	// of the state of the parent context.
	Run(context.Context, Task[V]) (V, error)
}

Runner is a task executor that honors retry semantics. A Runner is associated with a PolicyFactory, a ShouldRetry strategy, and one or more OnAttempt callbacks.

Example (Constant)
onAttempt := func(a Attempt[int]) {
	fmt.Println("Attempt.Result", a.Result, "Attempt.Next", a.Next)
}

runner, _ := NewRunner(
	WithOnAttempt(onAttempt),
	WithPolicyFactory[int](Config{
		Interval: 10 * time.Millisecond,
	}),
)

attempts := 0

result, _ := runner.Run(
	context.Background(),
	func(_ context.Context) (int, error) {
		fmt.Println("executing task ...")
		attempts++
		if attempts < 3 {
			return -1, errors.New("task error")
		}

		return 1234, nil
	},
)

fmt.Println("Run result", result)
Output:

executing task ...
Attempt.Result -1 Attempt.Next 10ms
executing task ...
Attempt.Result -1 Attempt.Next 10ms
executing task ...
Attempt.Result 1234 Attempt.Next 0s
Run result 1234
Example (Never)
onAttempt := func(a Attempt[int]) {
	fmt.Println("Attempt.Result", a.Result, "Attempt.Next", a.Next)
}

// no Config or PolicyFactory means this runner will never
// retry anything
runner, _ := NewRunner[int](
	WithOnAttempt(onAttempt),
)

result, _ := runner.Run(
	context.Background(),
	AddValue(1234, func() error {
		fmt.Println("executing task ...")
		return nil
	}),
)

fmt.Println("Run result", result)
Output:

executing task ...
Attempt.Result 1234 Attempt.Next 0s
Run result 1234

func NewRunner

func NewRunner[V any](opts ...RunnerOption[V]) (Runner[V], error)

NewRunner creates a Runner using the supplied set of options.

type RunnerOption

type RunnerOption[V any] interface {
	// contains filtered or unexported methods
}

RunnerOption is a configurable option for creating a task runner.

func WithImmediateTimer added in v0.0.3

func WithImmediateTimer[V any]() RunnerOption[V]

WithImmediateTimer configures the Runner with a Timer that immediately fires without waiting. Useful mainly for unit tests.

func WithOnAttempt

func WithOnAttempt[V any](fns ...OnAttempt[V]) RunnerOption[V]

WithOnAttempt appends one or more callbacks for task results. This option can be applied repeatedly, and the set of OnAttempt callbacks is cumulative.

func WithPolicyFactory

func WithPolicyFactory[V any](pf PolicyFactory) RunnerOption[V]

WithPolicyFactory returns a RunnerOption that assigns the given PolicyFactory to the created task runner.

Config in this package implements PolicyFactory.

func WithShouldRetry

func WithShouldRetry[V any](sr ShouldRetry[V]) RunnerOption[V]

WithShouldRetry adds a predicate to the created task runner that will be used to determine if an error should be retried or should halt further attempts. This predicate is used if the error itself does not expose retryablity semantics via a ShouldRetry method.

func WithTimer added in v0.0.3

func WithTimer[V any](t Timer) RunnerOption[V]

WithTimer supplies a custom Timer for the Runner. This option is primarily useful for testing.

type ShouldRetry

type ShouldRetry[V any] func(V, error) bool

ShouldRetry is a predicate for determining whether a task's results warrant a retry.

type ShouldRetryable

type ShouldRetryable interface {
	// ShouldRetry indicates whether this error should stop all future
	// retries.
	ShouldRetry() bool
}

ShouldRetryable is an interface that errors may implement to signal to an task runner whether an error should prevent or allow future retries.

type SimpleTask

type SimpleTask interface {
	~func() error | ~func(context.Context) error
}

SimpleTask is the underlying type for any task closure which doesn't return a custom value. Tasks of this type must be wrapped with a value, either in calling code or via one of the convenience functions in this package.

type Task

type Task[V any] func(context.Context) (V, error)

Task is the basic type of closure that can be retried in the face of errors. This package provides a few convenient ways of coercing functions with certain signatures into a Task that returns results of an arbitrary type V.

func AsTask

func AsTask[V any, RT RunnableTask[V]](rt RT) Task[V]

AsTask normalizes any RunnableTask into a Task so that it can be submitted to a Runner.

type Timer added in v0.0.3

type Timer func(time.Duration) (ch <-chan time.Time, stop func() bool)

Timer is a closure strategy for starting a timer. The returned stop function has the same semantics as time.Timer.Stop.

The default Timer used internally delegates to time.NewTimer. A custom Timer is primarily useful in unit tests.

Directories

Path Synopsis
Package retryhttp provides a simple client that retries HTTP transactions according to a policy.
Package retryhttp provides a simple client that retries HTTP transactions according to a policy.

Jump to

Keyboard shortcuts

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