Skip to content

ciceroverneck/backoff

Repository files navigation

backoff

Go Reference Go Report Card License: MIT

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.

About

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

Topics

Resources

License

Stars

Watchers

Forks

Contributors