backoff

package module
v2.0.0 Latest Latest
Warning

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

Go to latest
Published: Dec 16, 2025 License: MIT Imports: 5 Imported by: 0

README

GoDoc reference Lint Test

Backoff

Package backoff implements backoff logic for retry loops using Go 1.23+ iterators.

Usage

Retries with Attempts()

Use Attempts() for bounded retries. The iterator sleeps the backoff duration after each iteration before yielding the next attempt:

for attempt := range backoff.Attempts(ctx, time.Second, 30*time.Second, backoff.Exponential(2), 5) {
    if err := doSomething(); err == nil {
        return nil
    }
    log.Printf("attempt %d failed", attempt)
    // Sleep happens automatically before next iteration
}
return errors.New("max retries exceeded")
With Timeout

Use context.WithTimeout to limit total elapsed time:

ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()

for attempt := range backoff.Attempts(ctx, time.Second, 30*time.Second, backoff.Exponential(2), 10) {
    if err := doSomething(); err == nil {
        return nil
    }
}
return ctx.Err() // context.DeadlineExceeded after 2 minutes
With Jitter

Jitter is useful in distributed systems to avoid thundering herd problems:

for attempt := range backoff.Attempts(ctx, time.Second, 30*time.Second, backoff.Jitter(backoff.Exponential(2), 0.5), 5) {
    if err := doSomething(); err == nil {
        return nil
    }
}
Manual Sleep with Delays()

Use Delays() when you need control over the sleep. Unlike Attempts(), this iterator does not sleep—it yields the delay value for you to handle:

for attempt, delay := range backoff.Delays(ctx, time.Second, 30*time.Second, backoff.Exponential(2)) {
    if err := doSomething(); err == nil {
        return nil
    }
    log.Printf("attempt %d failed, retrying in %v", attempt, delay)
    time.Sleep(delay)
}
// Yields: (1, 1s), (2, 2s), (3, 4s), (4, 8s), (5, 16s), (6, 30s), ...

Step Functions

Function Description
Fixed() Always returns base (equivalent to Linear(0))
Linear(d) Adds d each iteration: base, base+d, base+2d, ...
Incremental(m) Multiplies base by count: base, base*m, base*2m, ...
Exponential(f) Exponential growth: base, base*f, base*f^2, ...
Jitter(fn, factor) Wraps any step function with randomized jitter in [d-factor*d, d]

Contributions

Contributions are welcome via Pull Requests.

Documentation

Overview

Package backoff implements backoff logic for loop controls.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Attempts

func Attempts(ctx context.Context, base, ceiling time.Duration, fn StepFunc, maxAttempts int) iter.Seq[int]

Attempts returns an iterator that yields attempt numbers (1-based), sleeping the backoff duration after each iteration before yielding the next attempt. The sleep respects context cancellation. The iterator stops when the context is cancelled or maxAttempts is reached.

Example

Example of using the package-level Attempts function.

for attempt := range backoff.Attempts(context.Background(), time.Millisecond, 10*time.Millisecond, backoff.Exponential(2), 5) {
	fmt.Printf("attempt %d\n", attempt)
}
Output:

attempt 1
attempt 2
attempt 3
attempt 4
attempt 5

func Delays

func Delays(ctx context.Context, base, ceiling time.Duration, fn StepFunc) iter.Seq2[int, time.Duration]

Delays returns an infinite iterator that yields the 1-based attempt number and the delay duration. The caller is responsible for sleeping the returned duration. The iterator stops when the context is cancelled.

Example

Example of using the package-level Delays function.

for attempt, delay := range backoff.Delays(context.Background(), time.Second, 30*time.Second, backoff.Exponential(2)) {
	fmt.Printf("attempt %d, delay %v\n", attempt, delay)
	if attempt == 3 {
		break
	}
}
Output:

attempt 1, delay 1s
attempt 2, delay 2s
attempt 3, delay 4s

Types

type Generator

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

Generator generates a sequence of duration values using a step function.

A Generator is not safe for concurrent use by multiple goroutines.

func New

func New(base, ceiling time.Duration, fn StepFunc) *Generator

New returns a new generator that uses the given step function.

Base is the first value that will be returned, and the one from which the next values will be derived. Ceiling is the maximum value that can be generated. A ceiling of 0 disables the ceiling check.

func (*Generator) Attempts

func (g *Generator) Attempts(ctx context.Context, maxAttempts int) iter.Seq[int]

Attempts returns an iterator that yields attempt numbers (1-based), sleeping the backoff duration after each iteration before yielding the next attempt. The sleep respects context cancellation.

The iterator stops when the context is cancelled or maxAttempts is reached.

Example

Example of using Attempts for automatic retries with sleep.

gen := backoff.New(time.Millisecond, 10*time.Millisecond, backoff.Exponential(2))
for attempt := range gen.Attempts(context.Background(), 5) {
	fmt.Printf("attempt %d\n", attempt)
}
Output:

attempt 1
attempt 2
attempt 3
attempt 4
attempt 5

func (*Generator) Base

func (g *Generator) Base() time.Duration

Base returns the base duration used by the generator.

func (*Generator) Ceiling

func (g *Generator) Ceiling() time.Duration

Ceiling returns the maximum duration that can be yielded.

func (*Generator) Delays

func (g *Generator) Delays(ctx context.Context) iter.Seq2[int, time.Duration]

Delays returns an infinite iterator that yields the 1-based attempt number and the delay duration. Unlike Generator.Attempts, this iterator does not sleep—the caller is responsible for sleeping the returned duration.

The iterator stops when the context is cancelled.

Example

Example of using Generator.Delays for manual control over sleep.

gen := backoff.New(time.Second, 30*time.Second, backoff.Exponential(2))
for attempt, delay := range gen.Delays(context.Background()) {
	fmt.Printf("attempt %d, delay %v\n", attempt, delay)
	// caller controls when to sleep, e.g.: time.Sleep(delay)
	if attempt == 3 {
		break
	}
}
Output:

attempt 1, delay 1s
attempt 2, delay 2s
attempt 3, delay 4s

func (*Generator) Reset

func (g *Generator) Reset()

Reset resets the generator to its initial state.

type StepFunc

type StepFunc func(base time.Duration, count int) time.Duration

StepFunc is the signature of the function that generates step values for a Generator.

func Exponential

func Exponential(factor int) StepFunc

Exponential returns an exponential step function.

Example

Example of an exponential backoff.

gen := backoff.New(time.Second, 30*time.Second, backoff.Exponential(2))
var values []time.Duration
for _, d := range gen.Delays(context.Background()) {
	values = append(values, d.Truncate(time.Second))
	if len(values) == 10 {
		break
	}
}
fmt.Println(values)
Output:

[1s 2s 4s 8s 16s 30s 30s 30s 30s 30s]

func Fixed

func Fixed() StepFunc

Fixed returns a StepFunc that always returns the base duration. It is equivalent to Linear(0).

Example

Example of a fixed backoff using the iterator API.

gen := backoff.New(time.Second, 10*time.Second, backoff.Fixed())
var values []time.Duration
for _, d := range gen.Delays(context.Background()) {
	values = append(values, d)
	if len(values) == 5 {
		break
	}
}
fmt.Println(values)
Output:

[1s 1s 1s 1s 1s]

func Incremental

func Incremental(multiplier int) StepFunc

Incremental returns a step function that scales the base by the given multiple each retry.

Example

Example of an incremental backoff.

gen := backoff.New(time.Second, 10*time.Second, backoff.Incremental(2))
var values []time.Duration
for _, d := range gen.Delays(context.Background()) {
	values = append(values, d)
	if len(values) == 10 {
		break
	}
}
fmt.Println(values)
Output:

[1s 2s 4s 6s 8s 10s 10s 10s 10s 10s]

func Jitter

func Jitter(fn StepFunc, factor float64) StepFunc

Jitter wraps a StepFunc and returns a new StepFunc that applies "full-range" jitter in the range [d - factor*d, d].

For example, with d=1s and factor=0.5, the result is uniformly distributed in [500ms, 1s]. Factor should be in [0, 1].

Example

Jitter is particularly useful with distributed systems.

gen := backoff.New(time.Second, 30*time.Second, backoff.Jitter(backoff.Exponential(2), 0.5))
var values []time.Duration
for _, d := range gen.Delays(context.Background()) {
	values = append(values, d.Truncate(time.Second))
	if len(values) == 5 {
		break
	}
}
fmt.Println(values)

func Linear

func Linear(d time.Duration) StepFunc

Linear returns a step function that adds d each retry.

Example

Example of a linear backoff.

gen := backoff.New(time.Second, 10*time.Second, backoff.Linear(2*time.Second))
var values []time.Duration
for _, d := range gen.Delays(context.Background()) {
	values = append(values, d)
	if len(values) == 5 {
		break
	}
}
fmt.Println(values)
Output:

[1s 3s 5s 7s 9s]