backoff

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: May 24, 2026 License: MIT Imports: 5 Imported by: 0

README

backoff

Go Reference Go Report Card

A simple, lightweight, interface-driven exponential backoff library for Go 1.26.3+ designed for retrying operations with random jitter.

Features

  • 100% Interface-Driven: Clean API exposing interfaces (Backoff, Option) allowing simple mocking and stubbing in consumer tests.
  • Modern Go: Leverages "math/rand/v2" for fast, auto-seeded, and thread-safe random jitter without memory allocations.
  • Resource Safe: Protects against memory leaks by explicitly stopping timers when contexts are cancelled.
  • Generics Support: Native helper backoff.Do[T] to execute operations that return both a value and an error in a type-safe way.
  • Permanent Errors: Define errors that immediately halt the retry loop using backoff.Permanent(err).
  • First-Class Testing Utilities: Dedicated backofftest package with customizable mocks, zero-delay executors, and error simulators.

Installation

go get github.com/ciceroverneck/backoff

Quick Start

Simple Retry
package main

import (
	"context"
	"log"
	"time"

	"github.com/ciceroverneck/backoff"
)

func main() {
	ctx := context.Background()

	// Configure a backoff mechanism
	boff := backoff.New(
		backoff.Exponential(),
		backoff.MaxAttempts(5),
		backoff.Interval(200 * time.Millisecond),
		backoff.Notify(func(err error, next time.Duration, count uint) {
			log.Printf("Attempt %d failed: %v. Retrying in %v...", count, err, next)
		}),
	)

	// Execute operation
	err := boff.Do(ctx, func(ctx context.Context) error {
		// Your logic here (e.g. database query, HTTP request)
		return nil
	})
	if err != nil {
		log.Fatalf("Operation failed: %v", err)
	}
}
Generic Support (Returning a Value)
val, err := backoff.Do(ctx, boff, func(ctx context.Context) (string, error) {
	// Execute logic that returns a result
	return "success data", nil
})
Aborting Retries with Permanent Errors

Use backoff.Permanent to wrap an error and signal to the retry loop that it should stop retrying immediately.

err := boff.Do(ctx, func(ctx context.Context) error {
	err := performAction()
	if isUnrecoverable(err) {
		return backoff.Permanent(err) // Stops retrying immediately
	}
	return err // Retries if count/time limits are not reached
})

Testing Utilities (backofftest)

The backofftest package simplifies unit testing for consumers of this library.

1. Bypass Delays with NewZero

Use backofftest.NewZero() to run the operation exactly once without any delays, retries, or timers, allowing tests to run instantly:

func TestMyService(t *testing.T) {
	// Injects a zero backoff to bypass delays in tests
	service := NewService(backofftest.NewZero())
	
	err := service.PerformAction()
	// Assertions...
}
2. Verify Actions and Stubs with NewMock

Use backofftest.NewMock to inspect called operations or mock custom error/retry scenarios:

func TestMyService_WithMock(t *testing.T) {
	// Create a mock that returns a custom error
	mockBoff := backofftest.NewMock(func(ctx context.Context, fn func(context.Context) error) error {
		_ = fn(ctx) // execute inner function
		return errors.New("mocked error")
	})

	service := NewService(mockBoff)
	_ = service.PerformAction()

	// Assertions on recorded calls
	if len(mockBoff.Calls()) != 1 {
		t.Errorf("expected 1 call, got %d", len(mockBoff.Calls()))
	}
}
3. Simulate Specific Error Conditions

Easily verify how your code handles backoff limit errors (such as ErrMaxRetries or ErrMaxElapsedTime):

// Simulate reaching max retries limit
mockBoff := backofftest.NewMockMaxRetries(errors.New("db error"))

err := mockBoff.Do(ctx, func(ctx context.Context) error {
	return errors.New("db error")
})

// err wraps backoff.ErrMaxRetries and the original db error
if errors.Is(err, backoff.ErrMaxRetries) {
	// Handle max retries path...
}

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package backoff implements a simple exponential backoff algorithm for retrying operations.

Use Do function for retrying operations that may fail.

Simple example:

boff := backoff.New(
	backoff.Exponential(),
	backoff.MaxAttempts(5),
	backoff.Notify(func(err error, next time.Duration, count uint) {
		log.Println(count, next, err)
	}),
)
err := boff.Do(ctx, func(ctx context.Context) error {
	err := performAction()
	if isUnrecoverable(err) {
		return backoff.Permanent(err)
	}
	return err
})

Example using generics to return a value:

val, err := backoff.Do(ctx, boff, func(ctx context.Context) (string, error) {
	// perform operation that returns (string, error)
	return "result", nil
})

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrMaxRetries is returned when the maximum number of retries is reached.
	ErrMaxRetries = errors.New("maximum retries reached")
	// ErrMaxElapsedTime is returned when the maximum elapsed time is reached.
	ErrMaxElapsedTime = errors.New("maximum elapsed time reached")
)

Functions

func Continue deprecated

func Continue(err error) error

Continue wraps an error to indicate that the backoff loop should stop and return this error immediately without further retries.

Deprecated: use Permanent instead.

func Do

func Do[T any](ctx context.Context, b Backoff, fn func(ctx context.Context) (T, error)) (T, error)

Do is a package-level generic function that runs the provided operation within the backoff retry loop and returns the value and error from the operation.

func Permanent

func Permanent(err error) error

Permanent wraps an error to indicate that the retry loop should stop immediately.

Types

type Backoff

type Backoff interface {
	Do(ctx context.Context, fn func(context.Context) error) error
}

Backoff manages the settings for exponential backoff retries. A Backoff instance is safe for concurrent use by multiple goroutines.

func New

func New(opt ...Option) Backoff

New creates a new Backoff instance with the provided options.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option configures backoff settings.

func Exponential

func Exponential() Option

Exponential enables exponential backoff behavior. If not specified, the backoff interval remains constant at the initial interval. Default is false (constant backoff interval).

func Interval

func Interval(duration time.Duration) Option

Interval sets the initial backoff interval. Values less than 0 will be clamped to 0. Default is 500 milliseconds.

func MaxAttempts added in v0.1.1

func MaxAttempts(v uint) Option

MaxAttempts sets the maximum number of attempts (including the initial one). If set to 0, there is no attempt limit. Default is 0 (unlimited attempts).

func MaxElapsedTime

func MaxElapsedTime(duration time.Duration) Option

MaxElapsedTime sets the maximum total duration allowed for retries, starting from the initial attempt. If exceeded, retries halt immediately. If set to 0, there is no time limit. Values less than 0 will be clamped to 0. Default is 0 (no time limit).

func MaxInterval

func MaxInterval(duration time.Duration) Option

MaxInterval sets the maximum duration for a single backoff interval. Once reached, the interval will not grow any further. Values less than 0 will be clamped to 0. Default is 60 seconds.

func MaxRetries deprecated

func MaxRetries(v uint) Option

MaxRetries sets the maximum number of retry attempts. If set to 0, there is no retry limit. Default is 0 (unlimited retries).

Deprecated: Use MaxAttempts instead to specify the total number of attempts.

func Multiplier

func Multiplier(multiplier float64) Option

Multiplier sets the multiplier used to increase the backoff interval exponentially. The value must be at least 1.0. Values less than 1.0 will be clamped to 1.0. Default is 1.5.

func Notify

func Notify(fn func(err error, duration time.Duration, count uint)) Option

Notify configures a notification callback triggered on each failed retry. The callback receives the error of the failed attempt, the sleep duration before the next attempt, and the attempt count (starting from 1). Default is nil (no notification).

func RandomizationFactor

func RandomizationFactor(factor float64) Option

RandomizationFactor sets the randomization factor to add jitter to the backoff intervals. The jitter adds a random variance between -factor and +factor to each base interval duration. The value must be between 0.0 and 1.0. Values outside this range will be clamped to 0.0 or 1.0. Default is 0.5 (adding up to ±50% jitter to the interval).

type PermanentError

type PermanentError interface {
	error
	Permanent() bool
}

PermanentError is an interface that errors can implement to indicate that the retry loop should stop immediately and return this error.

Directories

Path Synopsis
Package backofftest provides utilities for testing code that uses the backoff package.
Package backofftest provides utilities for testing code that uses the backoff package.

Jump to

Keyboard shortcuts

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