retry

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2026 License: MIT Imports: 5 Imported by: 0

README

retry

codecov

A simple, standalone Go package for retrying operations with exponential backoff and jitter support. Zero external dependencies.

Features

  • Generic API: Works with any return type using Go generics
  • Exponential Backoff: Configurable backoff with multiplier and max duration
  • Jitter Support: Add randomness to backoff delays to avoid thundering herd
  • Custom Retry Policies: Control which errors should be retried automatically
  • Debug Logging: Optional logging interface for observability
  • Zero Dependencies: Uses only Go standard library

Installation

go get github.com/rohmanhakim/retry

Quick Start

package main

import (
    "fmt"
    "time"
    
    "github.com/rohmanhakim/retry"
)

// Define a custom error that implements RetryableError
type NetworkError struct {
    msg string
}

func (e *NetworkError) Error() string {
    return e.msg
}

func (e *NetworkError) RetryPolicy() retry.RetryPolicy {
    return retry.RetryPolicyAuto // Will be retried automatically
}

func main() {
    // Configure retry parameters
    backoffParam := retry.NewBackoffParam(
        1*time.Second,  // Initial delay
        2.0,            // Multiplier (doubles each time)
        30*time.Second, // Maximum delay
    )
    
    params := retry.NewRetryParam(
        100*time.Millisecond, // Base delay
        50*time.Millisecond,  // Jitter
        42,                   // Random seed
        5,                    // Max attempts
        backoffParam,
    )
    
    // Define the operation to retry
    fn := func() (string, retry.RetryableError) {
        // Your operation here
        // Return RetryPolicyAuto error for transient failures
        // Return RetryPolicyManual or RetryPolicyNever for permanent failures
        return "success", nil
    }
    
    // Execute with retry
    result := retry.Retry(params, retry.NewNoOpLogger(), fn)
    
    if result.IsSuccess() {
        fmt.Printf("Success: %s (attempts: %d)\n", result.Value(), result.Attempts())
    } else {
        fmt.Printf("Failed: %v (attempts: %d)\n", result.Err(), result.Attempts())
    }
}

Retry Policies

The package supports three retry policies:

Policy Description
RetryPolicyAuto Error will be retried automatically with exponential backoff
RetryPolicyManual Error should not be auto-retried, but is eligible for manual retry
RetryPolicyNever Permanent failure, should not be retried at all

Implementing RetryableError

To control retry behavior, implement the RetryableError interface on your error types:

type MyError struct {
    msg       string
    transient bool
}

func (e *MyError) Error() string {
    return e.msg
}

func (e *MyError) RetryPolicy() retry.RetryPolicy {
    if e.transient {
        return retry.RetryPolicyAuto
    }
    return retry.RetryPolicyNever
}

Debug Logging

Implement the DebugLogger interface to add observability:

type MyLogger struct{}

func (l *MyLogger) Enabled() bool {
    return true
}

func (l *MyLogger) LogRetry(ctx context.Context, attempt, maxAttempts int, backoff time.Duration, err error) {
    if err != nil {
        log.Printf("Retry %d/%d: backoff=%v, error=%v", attempt, maxAttempts, backoff, err)
    } else {
        log.Printf("Success on attempt %d/%d", attempt, maxAttempts)
    }
}

Use retry.NewNoOpLogger() for zero-overhead when logging is not needed.

API Reference

Types
// RetryPolicy defines automatic retry behavior
type RetryPolicy int

const (
    RetryPolicyAuto   RetryPolicy = iota // Auto retry with backoff
    RetryPolicyManual                    // Manual retry only
    RetryPolicyNever                     // No retry
)

// RetryableError interface for error classification
type RetryableError interface {
    error
    RetryPolicy() RetryPolicy
}

// RetryParam holds retry configuration
type RetryParam struct {
    BaseDelay    time.Duration
    Jitter       time.Duration
    RandomSeed   int64
    MaxAttempts  int
    BackoffParam BackoffParam
}

// Result holds the outcome of a retry operation
type Result[T any] struct {
    // Contains value on success, zero value on failure
    // Contains error on failure, nil on success
    // Number of attempts made
}

// BackoffParam holds exponential backoff configuration
type BackoffParam struct {
    // Initial duration, multiplier, max duration
}

// DebugLogger interface for logging
type DebugLogger interface {
    Enabled() bool
    LogRetry(ctx context.Context, attempt, maxAttempts int, backoff time.Duration, err error)
}
Functions
// Retry executes fn with retry logic
func Retry[T any](retryParam RetryParam, logger DebugLogger, fn func() (T, RetryableError)) Result[T]

// NewRetryParam creates retry configuration
func NewRetryParam(baseDelay, jitter time.Duration, randomSeed int64, maxAttempts int, backoffParam BackoffParam) RetryParam

// NewBackoffParam creates backoff configuration
func NewBackoffParam(initialDuration time.Duration, multiplier float64, maxDuration time.Duration) BackoffParam

// NewNoOpLogger creates a no-op logger (zero overhead)
func NewNoOpLogger() *NoOpLogger

// NewRetryError creates a retry error
func NewRetryError(cause RetryErrorCause, message string, policy RetryPolicy, wrapped error) *RetryError

License

MIT License - see LICENSE for details.

Documentation

Overview

Package retry provides a generic retry mechanism with exponential backoff and jitter.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BackoffParam

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

BackoffParam holds the parameters for exponential backoff calculation. Example usage:

params := NewBackoffParam(
    1 * time.Second,  // Start with 1s
    2.0,              // Double each time
    30 * time.Second, // Cap at 30s
)

func NewBackoffParam

func NewBackoffParam(
	initialDuration time.Duration,
	multiplier float64,
	maxDuration time.Duration,
) BackoffParam

NewBackoffParam creates a new BackoffParam with the given settings.

func (*BackoffParam) InitialDuration

func (b *BackoffParam) InitialDuration() time.Duration

InitialDuration returns the initial backoff duration.

func (*BackoffParam) MaxDuration

func (b *BackoffParam) MaxDuration() time.Duration

MaxDuration returns the maximum backoff duration.

func (*BackoffParam) Multiplier

func (b *BackoffParam) Multiplier() float64

Multiplier returns the backoff multiplier.

type DebugLogger

type DebugLogger interface {
	// Enabled returns true if debug logging is enabled.
	// When false, the retry handler will skip logging entirely for efficiency.
	Enabled() bool

	// LogRetry logs retry attempts with backoff information.
	// Parameters:
	//   - ctx: context for the retry operation
	//   - attempt: current attempt number (1-based)
	//   - maxAttempts: maximum number of attempts allowed
	//   - backoff: the delay before the next retry (0 for success/exhausted)
	//   - err: the error that triggered the retry (nil on success)
	LogRetry(ctx context.Context, attempt int, maxAttempts int, backoff time.Duration, err error)
}

DebugLogger provides structured debug logging capabilities for the retry package. Users can implement this interface to provide custom logging behavior. When debug mode is disabled, use NoOpLogger for zero overhead.

type NoOpLogger

type NoOpLogger struct{}

NoOpLogger is a no-operation implementation of DebugLogger. It provides zero overhead when debug mode is disabled. All methods are empty and Enabled() always returns false.

func NewNoOpLogger

func NewNoOpLogger() *NoOpLogger

NewNoOpLogger creates a new NoOpLogger instance.

func (*NoOpLogger) Enabled

func (n *NoOpLogger) Enabled() bool

Enabled returns false - debug logging is disabled.

func (*NoOpLogger) LogRetry

func (n *NoOpLogger) LogRetry(_ context.Context, _ int, _ int, _ time.Duration, _ error)

LogRetry is a no-op.

type Result

type Result[T any] struct {
	// contains filtered or unexported fields
}

Result encapsulates the immutable outcome of a retry operation. It holds either a successful value or an error, along with metadata about the execution.

func NewFailureResult

func NewFailureResult[T any](err RetryableError, attempts int) Result[T]

NewFailureResult creates a Result representing a failed retry operation.

func NewSuccessResult

func NewSuccessResult[T any](value T, attempts int) Result[T]

NewSuccessResult creates a Result representing a successful retry operation.

func Retry

func Retry[T any](retryParam RetryParam, logger DebugLogger, fn func() (T, RetryableError)) Result[T]

Retry executes the provided function with retry logic. It will retry the function up to MaxAttempts times, applying exponential backoff with jitter between attempts. Only retryable errors will trigger a retry.

Type parameter T represents the return type of the function being retried. Returns a Result containing the value (if successful), error (if failed), and the number of attempts made.

The logger parameter provides debug logging capabilities. When debug mode is disabled (NoOpLogger), there is zero overhead from logging.

func (Result[T]) Attempts

func (r Result[T]) Attempts() int

Attempts returns the number of attempts made before success or failure.

func (Result[T]) Err

func (r Result[T]) Err() RetryableError

Err returns the error if the operation failed, or nil if successful.

func (Result[T]) IsFailure

func (r Result[T]) IsFailure() bool

IsFailure returns true if the operation failed (has error).

func (Result[T]) IsSuccess

func (r Result[T]) IsSuccess() bool

IsSuccess returns true if the operation succeeded (no error).

func (Result[T]) Value

func (r Result[T]) Value() T

Value returns the successful result value. Returns zero value of T if the operation failed.

type RetryError

type RetryError struct {
	Message string
	Cause   RetryErrorCause
	// contains filtered or unexported fields
}

RetryError represents an error that occurred during retry attempts. It stores the original error for debugging and implements the RetryableError interface.

func NewRetryError

func NewRetryError(cause RetryErrorCause, message string, policy RetryPolicy, wrapped error) *RetryError

NewRetryError creates a new RetryError with explicit classification. Parameters:

  • cause: The error cause (ErrZeroAttempt or ErrExhaustedAttempts)
  • message: Human-readable error message
  • policy: RetryPolicyAuto, RetryPolicyManual, or RetryPolicyNever
  • wrapped: The original error that caused the retry failure (may be nil)

func (*RetryError) Error

func (e *RetryError) Error() string

Error returns the error message implementing the error interface.

func (*RetryError) Is

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

Is allows errors.Is to match RetryError types.

func (*RetryError) RetryPolicy

func (e *RetryError) RetryPolicy() RetryPolicy

RetryPolicy returns the automatic retry behavior for this error. When RetryError is returned (exhausted attempts), it returns the cached policy.

func (*RetryError) Unwrap

func (e *RetryError) Unwrap() error

Unwrap returns the wrapped error for error chain support.

type RetryErrorCause

type RetryErrorCause string

RetryErrorCause represents the cause of a retry error.

const (
	// ErrZeroAttempt indicates that MaxAttempts was set to 0.
	ErrZeroAttempt RetryErrorCause = "zero attempt"

	// ErrExhaustedAttempts indicates that all retry attempts were exhausted.
	ErrExhaustedAttempts RetryErrorCause = "exhausted attempt"
)

type RetryParam

type RetryParam struct {
	BaseDelay    time.Duration
	Jitter       time.Duration
	RandomSeed   int64
	MaxAttempts  int
	BackoffParam BackoffParam
}

RetryParam holds the parameters for retry logic. These parameters are passed from outside (e.g., config) and should not be known by the retry handler internally.

func NewRetryParam

func NewRetryParam(
	baseDelay time.Duration,
	jitter time.Duration,
	randomSeed int64,
	maxAttempts int,
	backoffParam BackoffParam,
) RetryParam

NewRetryParam creates a new RetryParam with the given settings.

type RetryPolicy

type RetryPolicy int

RetryPolicy defines automatic retry behavior. This controls whether Retry() will attempt exponential backoff.

const (
	// RetryPolicyAuto indicates the error should be retried automatically
	// with exponential backoff.
	RetryPolicyAuto RetryPolicy = iota

	// RetryPolicyManual indicates the error should not be auto-retried,
	// but is eligible for manual retry.
	RetryPolicyManual

	// RetryPolicyNever indicates a permanent failure that should not be retried.
	RetryPolicyNever
)

type RetryableError

type RetryableError interface {
	error

	// RetryPolicy controls automatic retry behavior.
	// Used by the retry handler to decide whether to retry.
	RetryPolicy() RetryPolicy
}

RetryableError is an interface that errors must implement to be handled by the retry mechanism. Users should implement this interface on their custom error types to control retry behavior.

Jump to

Keyboard shortcuts

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