retry

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2025 License: MIT Imports: 7 Imported by: 0

README

retry-go: Production Retry Logic for Go

Release Software License Go Report Card Go Reference

Zero dependencies. Memory-bounded. Uptime-focused.

Because 99.99% uptime means your retry logic can't be the failure point.

Actively maintained fork of avast/retry-go focused on correctness and resource efficiency. 100% API compatible drop-in replacement.

Key improvements:

  • ⚡ Zero external dependencies
  • 🔒 Memory-bounded error accumulation
  • 🛡️ Integer overflow protection in backoff
  • 🎯 Enhanced readability and debuggability
  • 📊 Predictable behavior under load

Quick Start

Basic Retry with Error Handling
import (
    "net/http"
    "github.com/codeGROOVE-dev/retry-go"
)

// Retry API call with exponential backoff + jitter
err := retry.Do(
    func() error {
        resp, err := http.Get("https://api.stripe.com/v1/charges")
        if err != nil {
            return err
        }
        defer resp.Body.Close()
        
        if resp.StatusCode >= 500 {
            return fmt.Errorf("server error: %d", resp.StatusCode)
        }
        return nil
    },
    retry.Attempts(5),
    retry.DelayType(retry.CombineDelay(retry.BackOffDelay, retry.RandomDelay)),
)
Retry with Data Return (Generics)
import (
    "context"
    "encoding/json"
    "github.com/codeGROOVE-dev/retry-go"
)

// Database query with timeout and retry
users, err := retry.DoWithData(
    func() ([]User, error) {
        return db.FindActiveUsers(ctx)
    },
    retry.Attempts(3),
    retry.Context(ctx),
    retry.DelayType(retry.BackOffDelay),
)
Production Configuration
// Payment processing with comprehensive retry logic
err := retry.Do(
    func() error { return paymentGateway.Charge(ctx, amount) },
    retry.Attempts(5),
    retry.Context(ctx),
    retry.DelayType(retry.FullJitterBackoffDelay),
    retry.MaxDelay(30*time.Second),
    retry.OnRetry(func(n uint, err error) {
        log.Warn("Payment retry", "attempt", n+1, "error", err)
    }),
    retry.RetryIf(func(err error) bool {
        return !isAuthError(err) // Don't retry 4xx errors
    }),
)

Key Features

Unrecoverable Errors - Stop immediately for certain errors:

if resp.StatusCode == 401 {
    return retry.Unrecoverable(errors.New("auth failed"))
}

Context Integration - Timeout and cancellation:

ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
retry.Do(dbQuery, retry.Context(ctx))

Error-Specific Limits - Different retry counts per error type:

retry.AttemptsForError(1, sql.ErrTxDone)  // Don't retry transaction errors

Performance & Scale

Metric Value
Memory overhead ~200 bytes per operation
Error accumulation Bounded at 1000 errors (DoS protection)
Goroutines Uses calling goroutine only
High-throughput safe No hidden allocations or locks

Library Comparison

cenkalti/backoff - Complex interface, requires manual retry loops, no error accumulation.

sethgrid/pester - HTTP-only, lacks general-purpose retry logic.

matryer/try - Popular but non-standard API, missing production features.

rafaeljesus/retry-go - Similar design but lacks error-specific limits and comprehensive context handling.

This fork builds on avast/retry-go's solid foundation with correctness fixes and resource optimizations.

Installation

go get github.com/codeGROOVE-dev/retry-go

Documentation


Production retry logic that just works.

Documentation

Overview

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)
	},
)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BackOffDelay

func BackOffDelay(attempt uint, _ error, config *Config) time.Duration

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

func Do

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

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.

func FixedDelay

func FixedDelay(_ uint, _ error, config *Config) time.Duration

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

func FullJitterBackoffDelay

func FullJitterBackoffDelay(attempt uint, err error, config *Config) time.Duration

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

func IsRecoverable

func IsRecoverable(err error) bool

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

func RandomDelay

func RandomDelay(_ uint, _ error, config *Config) time.Duration

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

func Unrecoverable

func Unrecoverable(err error) error

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

Types

type Config

type Config struct {
	// contains filtered or unexported fields
}

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.

type DelayTypeFunc

type DelayTypeFunc func(attempt uint, err error, config *Config) time.Duration

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

func CombineDelay

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.

type Error

type Error []error

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.

func (Error) As

func (e Error) As(target interface{}) bool

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.

func (Error) Error

func (e Error) Error() string

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

func (Error) Is

func (e Error) Is(target error) bool

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

func (Error) Unwrap

func (e Error) Unwrap() error

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 (Error) WrappedErrors

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.

type OnRetryFunc

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

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

func Attempts(attempts uint) Option

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

func AttemptsForError

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.

func Context

func Context(ctx context.Context) Option

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),
)

func Delay

func Delay(delay time.Duration) Option

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

func DelayType

func DelayType(delayType DelayTypeFunc) Option

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

func LastErrorOnly

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).

func MaxDelay

func MaxDelay(maxDelay time.Duration) Option

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).

func MaxJitter

func MaxJitter(maxJitter time.Duration) Option

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

func OnRetry

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

func RetryIf(retryIf RetryIfFunc) 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

func UntilSucceeded() Option

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

func WithTimer

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

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

type RetryIfFunc func(error) bool

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

type RetryableFunc

type RetryableFunc func() error

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

type RetryableFuncWithData

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

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

type Timer

type Timer interface {
	// After returns a channel that sends the current time after the duration elapses.
	// It should behave like time.After.
	After(time.Duration) <-chan time.Time
}

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.

Jump to

Keyboard shortcuts

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