or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

backoff-policies.mdindex.mdretry-operations.mdticker.md
tile.json

backoff-policies.mddocs/

Backoff Policies

Backoff policies determine the wait duration between retry attempts. The package provides several built-in policies and allows custom implementations through the BackOff interface.

Core BackOff Interface

// BackOff is a backoff policy for retrying an operation
type BackOff interface {
    // NextBackOff returns the duration to wait before retrying the operation,
    // or backoff.Stop to indicate that no more retries should be made
    NextBackOff() time.Duration

    // Reset to initial state
    Reset()
}

Exponential Backoff

ExponentialBackOff increases the backoff period exponentially for each retry attempt using randomization to add jitter. This is the most commonly used backoff strategy.

ExponentialBackOff Type

type ExponentialBackOff struct {
    // Initial interval between retries
    InitialInterval time.Duration

    // Randomization factor (0.0 to 1.0) to add jitter to intervals
    RandomizationFactor float64

    // Multiplier for interval increase after each retry
    Multiplier float64

    // Maximum interval between retries (caps exponential growth)
    MaxInterval time.Duration

    // Maximum total elapsed time (0 = never stop based on time)
    // After this time, NextBackOff() returns Stop
    MaxElapsedTime time.Duration

    // Duration returned when stopping
    Stop time.Duration

    // Clock interface for time measurement
    Clock Clock
}

Constructor and Configuration

// Create ExponentialBackOff with default values and optional configuration
func NewExponentialBackOff(opts ...ExponentialBackOffOpts) *ExponentialBackOff
// Option function type for configuring ExponentialBackOff
type ExponentialBackOffOpts func(*ExponentialBackOff)

// Set initial interval between retries
func WithInitialInterval(duration time.Duration) ExponentialBackOffOpts

// Set randomization factor to add jitter to intervals
func WithRandomizationFactor(randomizationFactor float64) ExponentialBackOffOpts

// Set multiplier for increasing interval after each retry
func WithMultiplier(multiplier float64) ExponentialBackOffOpts

// Set maximum interval between retries
func WithMaxInterval(duration time.Duration) ExponentialBackOffOpts

// Set maximum total time for retries
func WithMaxElapsedTime(duration time.Duration) ExponentialBackOffOpts

// Set duration returned when stopping
func WithRetryStopDuration(duration time.Duration) ExponentialBackOffOpts

// Set custom clock for time measurement
func WithClockProvider(clock Clock) ExponentialBackOffOpts

ExponentialBackOff Methods

// Calculate next backoff interval using exponential formula with jitter
func (b *ExponentialBackOff) NextBackOff() time.Duration

// Reset interval to initial value and restart timer
func (b *ExponentialBackOff) Reset()

// Get elapsed time since creation or last reset
// Thread-safe: Can be called even while the backoff policy is used by a running ticker
func (b *ExponentialBackOff) GetElapsedTime() time.Duration

Default Configuration Values

const (
    DefaultInitialInterval     = 500 * time.Millisecond
    DefaultRandomizationFactor = 0.5
    DefaultMultiplier          = 1.5
    DefaultMaxInterval         = 60 * time.Second
    DefaultMaxElapsedTime      = 15 * time.Minute
)

Usage Example

import (
    "time"
    "github.com/cenkalti/backoff/v4"
)

// Using default configuration
b := backoff.NewExponentialBackOff()

// Custom configuration
b := backoff.NewExponentialBackOff(
    backoff.WithInitialInterval(1*time.Second),
    backoff.WithMaxInterval(30*time.Second),
    backoff.WithMaxElapsedTime(5*time.Minute),
    backoff.WithMultiplier(2.0),
    backoff.WithRandomizationFactor(0.3),
)

operation := func() error {
    // Your operation
    return nil
}

err := backoff.Retry(operation, b)

How Exponential Backoff Works

NextBackOff() is calculated using the following formula:

randomized interval = RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])

For example, with RetryInterval = 2, RandomizationFactor = 0.5, and Multiplier = 2:

  • The actual backoff period will range between 1 and 3 seconds (2 ± 50%)
  • After the first retry, the interval becomes 4 seconds (2 * 2)
  • The randomized interval for the second retry ranges between 2 and 6 seconds (4 ± 50%)

Note: MaxInterval caps the RetryInterval (before randomization), not the final randomized interval.

Example sequence with default arguments over 10 tries:

RequestRetryIntervalRandomized Interval Range
10.5s[0.25s, 0.75s]
20.75s[0.375s, 1.125s]
31.125s[0.562s, 1.687s]
41.687s[0.843s, 2.53s]
52.53s[1.265s, 3.795s]
63.795s[1.897s, 5.692s]
75.692s[2.846s, 8.538s]
88.538s[4.269s, 12.807s]
912.807s[6.403s, 19.210s]
1019.210sbackoff.Stop

Constant Backoff

ConstantBackOff always returns the same backoff delay, useful when you want fixed intervals between retries.

type ConstantBackOff struct {
    // Interval to return for each retry
    Interval time.Duration
}

// Create constant backoff with specified interval
func NewConstantBackOff(d time.Duration) *ConstantBackOff

// Returns constant interval
func (b *ConstantBackOff) NextBackOff() time.Duration

// No-op for constant backoff
func (b *ConstantBackOff) Reset()

Usage Example

import (
    "time"
    "github.com/cenkalti/backoff/v4"
)

// Retry every 5 seconds
b := backoff.NewConstantBackOff(5 * time.Second)

err := backoff.Retry(operation, b)

Zero Backoff

ZeroBackOff always returns zero duration, causing immediate retries without any delay.

type ZeroBackOff struct{}

// Always returns 0 (immediate retry)
func (b *ZeroBackOff) NextBackOff() time.Duration

// No-op
func (b *ZeroBackOff) Reset()

Usage Example

import "github.com/cenkalti/backoff/v4"

// Retry immediately without delay
b := &backoff.ZeroBackOff{}

err := backoff.Retry(operation, b)

Warning: ZeroBackOff will retry indefinitely without delay. Always combine with context timeout or WithMaxRetries to prevent infinite loops.

Stop Backoff

StopBackOff always returns the Stop constant, meaning the operation should never be retried.

type StopBackOff struct{}

// Always returns backoff.Stop (never retry)
func (b *StopBackOff) NextBackOff() time.Duration

// No-op
func (b *StopBackOff) Reset()
// Constant indicating no more retries should be made
const Stop time.Duration = -1

Usage Example

import "github.com/cenkalti/backoff/v4"

// Execute once, never retry
b := &backoff.StopBackOff{}

err := backoff.Retry(operation, b)

Backoff Wrappers

Context Wrapper

Wrap any backoff to stop when context is canceled.

// BackOff with context cancellation support
// This interface embeds BackOff, so it includes NextBackOff() and Reset() methods
// plus the Context() method for accessing the associated context
type BackOffContext interface {
    BackOff
    Context() context.Context
}

// Wrap backoff to stop on context cancellation
// Panics if ctx is nil
// If b is already a BackOffContext, it unwraps and rewraps with the new context
// to prevent nested wrapping
func WithContext(b BackOff, ctx context.Context) BackOffContext

Usage Example

import (
    "context"
    "time"
    "github.com/cenkalti/backoff/v4"
)

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

b := backoff.WithContext(
    backoff.NewExponentialBackOff(),
    ctx,
)

err := backoff.Retry(operation, b)
// Will stop retrying when context times out or is canceled

Max Retries Wrapper

Limit the number of retry attempts.

// Wrap backoff to limit maximum retry attempts
// Returns Stop if NextBackOff() has been called too many times
// since the last Reset()
func WithMaxRetries(b BackOff, max uint64) BackOff

Usage Example

import "github.com/cenkalti/backoff/v4"

// Retry at most 3 times
b := backoff.WithMaxRetries(
    backoff.NewExponentialBackOff(),
    3,
)

err := backoff.Retry(operation, b)
// Will stop after 3 retry attempts

Note: Implementation is not thread-safe.

Clock Interface

Custom clock interface for time measurement, useful for testing.

// Clock is an interface that returns current time for BackOff
type Clock interface {
    Now() time.Time
}

// Default clock implementation using time.Now()
var SystemClock = systemClock{}

Usage Example

import "github.com/cenkalti/backoff/v4"

// Use custom clock for testing
type MockClock struct {
    currentTime time.Time
}

func (c *MockClock) Now() time.Time {
    return c.currentTime
}

mockClock := &MockClock{currentTime: time.Now()}

b := backoff.NewExponentialBackOff(
    backoff.WithClockProvider(mockClock),
)

Implementation Notes

  • Thread Safety: ExponentialBackOff and wrapper implementations are NOT thread-safe
  • MaxElapsedTime: When set to 0, ExponentialBackOff will never stop based on elapsed time
  • Randomization: Helps prevent thundering herd problems when multiple clients retry simultaneously
  • MaxInterval Cap: MaxInterval caps the interval before randomization is applied