retry package - github.com/codeGROOVE-dev/retry - Go Packages (original) (raw)

Package retry provides a simple and flexible retry mechanism for Go. It allows executing functions with automatic retry on failure, with configurable backoff strategies, retry conditions, and error handling.

The package is safe for concurrent use.

The package is inspired by Try::Tiny::Retry from Perl.

Basic usage:

url := "http://example.com" var body []byte

err := retry.Do( func() error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() body, err = io.ReadAll(resp.Body) if err != nil { return err } return nil }, )

With data return:

body, err := retry.DoWithData( func() ([]byte, error) { resp, err := http.Get(url) if err != nil { return nil, err } defer resp.Body.Close() return io.ReadAll(resp.Body) }, )

This section is empty.

This section is empty.

BackOffDelay implements exponential backoff delay strategy. Each retry attempt doubles the delay, up to a maximum.

func Do(retryableFunc RetryableFunc, opts ...Option) error

Do executes the retryable function with the provided options. It returns nil if the function succeeds, or an error if all retry attempts fail.

By default, it will retry up to 10 times with exponential backoff and jitter. The behavior can be customized using Option functions.

func DoWithData[T any](retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error)

DoWithData executes the retryable function with the provided options and returns the function's data. It returns the data and nil error if the function succeeds, or zero value and error if all retry attempts fail.

By default, it will retry up to 10 times with exponential backoff and jitter. The behavior can be customized using Option functions.

FixedDelay implements a constant delay strategy. The delay is always config.delay regardless of attempt number.

FullJitterBackoffDelay implements exponential backoff with full jitter. It returns a random delay between 0 and min(maxDelay, baseDelay * 2^attempt).

IsRecoverable reports whether err is recoverable. It returns false if err is or wraps an unrecoverable error.

func RandomDelay

RandomDelay implements a random delay strategy. Returns a random duration between 0 and config.maxJitter.

Unrecoverable wraps an error to mark it as unrecoverable. When an unrecoverable error is returned, the retry mechanism will stop immediately.

Config holds all configuration options for retry behavior. It is typically populated using Option functions and should not be constructed directly. Use the various Option functions like Attempts, Delay, and RetryIf to configure retry behavior.

DelayTypeFunc calculates the delay duration before the next retry attempt. The attempt parameter is the zero-based index of the attempt.

func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc

CombineDelay creates a DelayTypeFunc that sums the delays from multiple strategies. The total delay is capped at math.MaxInt64 to prevent overflow.

Error represents a collection of errors that occurred during retry attempts. It implements the error interface and provides compatibility with errors.Is, errors.As, and errors.Unwrap.

As finds the first error in e that matches target, and if so, sets target to that error value and returns true. It implements support for errors.As.

Error returns a string representation of all errors that occurred during retry attempts. Each error is prefixed with its attempt number.

Is reports whether any error in e matches target. It implements support for errors.Is.

Unwrap returns the last error for compatibility with errors.Unwrap. When you need to unwrap all errors, you should use WrappedErrors instead.

Example:

err := Do( func() error { return errors.New("original error") }, Attempts(1), ) fmt.Println(errors.Unwrap(err)) // "original error" is printed

func (e Error) WrappedErrors() []error

WrappedErrors returns the list of errors that this Error is wrapping. It is an implementation of the `errwrap.Wrapper` interface in package [errwrap](https://github.com/hashicorp/errwrap) so that `retry.Error` can be used with that library.

IfFunc is the signature for functions that determine whether to retry after an error. It returns true if the error is retryable, false otherwise.

type OnRetryFunc func(attempt uint, err error)

OnRetryFunc is the signature for functions called after each retry attempt. The attempt parameter is the zero-based index of the attempt.

type Option func(*Config)

Option configures retry behavior. Options are applied in the order provided to Do or DoWithData. Later options override earlier ones if they modify the same configuration field.

func Attempts(attempts uint) Option

Attempts sets the maximum number of retry attempts. Setting to 0 enables infinite retries. Default is 10.

func AttemptsForError(attempts uint, err error) Option

AttemptsForError sets a specific number of retry attempts for a particular error. These attempts are counted against the total retry limit. The retry stops when either the specific error limit or total limit is reached. Note: errors are compared using errors.Is for matching.

Context sets the context for retry operations. The retry loop will stop if the context is cancelled or times out. Default is context.Background().

Example with timeout:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel()

retry.Do( func() error { return doSomething() }, retry.Context(ctx), )

Delay sets the base delay duration between retry attempts. Default is 100ms. The actual delay may be modified by the DelayType function.

func DelayType(delayType DelayTypeFunc) Option

DelayType sets the delay calculation function between retries. Default is CombineDelay(BackOffDelay, RandomDelay).

func LastErrorOnly(lastErrorOnly bool) Option

LastErrorOnly configures whether to return only the last error that occurred, or wrap all errors together. Default is false (return all errors).

MaxDelay sets the maximum delay duration between retry attempts. This caps the delay calculated by DelayType functions. By default, there is no maximum (0 means no limit).

MaxJitter sets the maximum random jitter duration for RandomDelay. Default is 100ms.

func OnRetry(onRetry OnRetryFunc) Option

OnRetry sets a callback function that is called after each failed attempt. This is useful for logging or other side effects.

Example:

retry.Do( func() error { return errors.New("some error") }, retry.OnRetry(func(attempt uint, err error) { log.Printf("#%d: %s\n", attempt, err) }), )

func RetryIf(retryIf IfFunc) Option

RetryIf controls whether a retry should be attempted after an error (assuming there are any retry attempts remaining)

skip retry if special error example:

retry.Do( func() error { return errors.New("special error") }, retry.RetryIf(func(err error) bool { if err.Error() == "special error" { return false } return true }) )

By default RetryIf stops execution if the error is wrapped using `retry.Unrecoverable`, so above example may also be shortened to:

retry.Do( func() error { return retry.Unrecoverable(errors.New("special error")) } )

func UntilSucceeded() Option

UntilSucceeded configures infinite retry attempts until success. Equivalent to Attempts(0).

func WithTimer(t Timer) Option

WithTimer provides a way to swap out timer implementations. This is primarily useful for testing.

Example:

type mockTimer struct{}

func (mockTimer) After(d time.Duration) <-chan time.Time { return time.After(0) // immediate return for tests }

retry.Do( func() error { ... }, retry.WithTimer(mockTimer{}), )

func WrapContextErrorWithLastError(wrap bool) Option

WrapContextErrorWithLastError configures whether to wrap context errors with the last function error. This is useful when using infinite retries (Attempts(0)) with context cancellation, as it preserves information about what error was occurring when the context expired. Default is false.

ctx, cancel := context.WithCancel(context.Background()) defer cancel()

retry.Do( func() error { ... }, retry.Context(ctx), retry.Attempts(0), retry.WrapContextErrorWithLastError(true), )

type RetryIfFunc = IfFunc

RetryIfFunc is an alias for IfFunc for backwards compatibility. Deprecated: Use IfFunc instead.

type RetryableFunc func() error

RetryableFunc is the function signature for retryable functions used with Do.

type RetryableFuncWithData[T any] func() (T, error)

RetryableFuncWithData is the function signature for retryable functions that return data. Used with DoWithData.

Timer provides an interface for time operations in retry logic. This abstraction allows for mocking time in tests and implementing custom timing behaviors. The standard implementation uses time.After.

Security note: Custom Timer implementations must return a valid channel that either receives a time value or blocks. Returning nil will cause the retry to fail immediately.