backoff

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

Release Reference DeepWiki Test

Insights Insights

go-backoff

Backoff algorithm implementations for Go.

Features

  • Support various backoff algorithms
  • Offset support
  • Jitter support
  • Function retrying
  • Zero dependency

Supported backoff algorithms:

See the API doc for details.

  • Fixed backoff: d = interval
  • Random backoff: d = offset + random(limit-offset)
  • Linear backoff: d = offset + coeff * n
  • Polynomial backoff: d = offset + coeff * n^exponent
  • Exponential backoff: d = offset + coeff * base^n
  • Fibonacci backoff: d = offset + coeff * fibonacci(n)

Usages

Basic backoff usage

Instanciate a backoff provider with paramerters. The Attempt(n int) returns the n-th backoff duration instead of blocking the call.

// Create a config.
// All params are optional. Set to override default values.
linear := &backoff.LinearConfig{
  Offset: 0 * time.Millisecond,
  Limit:  200 * time.Millisecond,
  Coeff:  5 * time.Millisecond,
  Jitter: backoff.NoJitter,
}

bo, err := linear.New()
if err != nil {
  panic(err)
}

for i := range 11 {
  d := bo.Attempt(i)
  fmt.Printf("%03d: %s\n", i, d)
}
// Output:
// 000: 0s
// 001: 5ms
// 002: 10ms
// 003: 15ms
// 004: 20ms
// 005: 25ms
// 006: 30ms
// 007: 35ms
// 008: 40ms
// 009: 45ms
// 010: 50ms
Run retryable functions

The Retryer provides running and retrying functions with specified backoff strategy. Retry count can be obtained by RetryCount().

Basic usage:

// Create a new backoff.
bo, _ := (&backoff.FixedConfig{}).New()

// Create a new retryer with backoff and other params.
r := backoff.NewRetryer(bo)
r.MaxRetry = 100
r.MaxElapsedTime = 0 // No timeout

// Run a retryable function.
err = r.Run(func(n *backoff.Notify) error {
  fmt.Println("Retry count:", n.RetryCount())
  return nil
})
Stop retrying with success

Tell retryer to stop retrying using notifier's Success(). The returned err will always be nil.

See also ExampleRetryer_success.

bo, _ := (&backoff.FixedConfig{}).New()

r := backoff.NewRetryer(bo)
r.MaxRetry = 10

err := r.Run(func(n *backoff.Notify) error {
  count := n.RetryCount()
  if count == 3 {
    n.Success() // Tell retryer to stop retrying.
    return nil
  }
  return fmt.Errorf("Err-%d", count)
})
Stop retrying with non-success

Tell retryer to stop retrying using notifier's Stop().

See also ExampleRetryer_stop.

bo, _ := (&backoff.FixedConfig{}).New()

r := backoff.NewRetryer(bo)
r.MaxRetry = 10

err := r.Run(func(n *backoff.Notify) error {
  count := n.RetryCount()
  if count == 3 {
    n.Stop() // Tell retryer to stop retrying.
    return errors.New("Err-stop")
  }
  return fmt.Errorf("Err-%d", count)
})
Error handling

Errors from the Retryer can be identified like below.

err = r.RunContext(ctx, func(n *backoff.Notify) error {
  // Your codes.
  return nil
})

if err != nil {
  switch {
  case errors.Is(err, context.Canceled):
    // Given ctx was canceled.
    // This won't happen when Run, not RunContext, is used.
  case errors.Is(err, context.DeadlineExceeded):
    // Given ctx's deadline was exceeded.
    // This won't happen when Run, not RunContext, is used.
  case errors.Is(err, backoff.ErrMaxRetry):
    // Maximum retry count was reached.
  case errors.Is(err, backoff.ErrTimeout):
    // Maximum elapsed time was reached.
  default:
    // The retryable function told retryer to stop retrying.
    // The err contains all errors returned by the retryable function. 
  }
}

Docs & Examples

Benchmarks

Benchmarks of calling Attempt(10).

See benchmark_test.go for test conditions.

BenchmarkFixed-8       1000000000    0.6935 ns/op   0 B/op   0 allocs/op
BenchmarkRandom-8       125753828    9.833  ns/op   0 B/op   0 allocs/op
BenchmarkLinear-8        84624442   12.98   ns/op   0 B/op   0 allocs/op
BenchmarkPolynomial-8    32345972   37.23   ns/op   0 B/op   0 allocs/op
BenchmarkExponential-8   48786039   27.31   ns/op   0 B/op   0 allocs/op
BenchmarkFibonacci-8    121107502   10.13   ns/op   0 B/op   0 allocs/op

References

Documentation

Index

Examples

Constants

View Source
const (
	NoJitter     float64 = 0.0
	FullJitter   float64 = 1.0
	EqualJitter  float64 = 0.5
	HalfJitter   float64 = 0.5
	QuaterJitter float64 = 0.25
)

Variables

View Source
var (
	// ErrMaxRetry is returned by [Retryer.RunContext] when the retryable
	// function did not succeed within MaxRetry attempts.
	ErrMaxRetry = errors.New("go-backoff/backoff: max retry count reached")
	// ErrTimeout is returned by [Retryer.RunContext] when MaxElapsedTime
	// is reached before the retryable function succeeds.
	ErrTimeout = errors.New("go-backoff/backoff: max elapsed time reached")
)

Functions

This section is empty.

Types

type Backoff

type Backoff interface {
	// Attempt returns the n-th backoff duration.
	// Returned duration can be zero or positive.
	// n<=0 always returns zero.
	Attempt(n int) time.Duration
}

Backoff provides backoff algorithm.

type Error

type Error struct {
	Inner error  // Inner is the inner error.
	Type  string // Type is the error type.
	Alg   string // Alg is the backoff algorithm.
	Msg   string // Msg is the error message.
}

Error is the backoff error.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

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

func (*Error) Unwrap

func (e *Error) Unwrap() error

type Exponential

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

Exponential provides exponential backoff strategy.

Example
package main

import (
	"fmt"
	"time"

	"github.com/aileron-projects/go-backoff"
)

func main() {
	exponential := &backoff.ExponentialConfig{
		Offset: 0 * time.Millisecond,
		Limit:  600 * time.Second,
		Coeff:  1 * time.Millisecond,
		Base:   1.2,
		Jitter: backoff.NoJitter,
	}
	bo, err := exponential.New()
	if err != nil {
		panic(err)
	}

	for i := range 11 {
		d := bo.Attempt(i)
		fmt.Printf("%03d: %s\n", i, d)
	}
}
Output:
000: 0s
001: 1.2ms
002: 1.44ms
003: 1.728ms
004: 2.0736ms
005: 2.48832ms
006: 2.985983ms
007: 3.58318ms
008: 4.299816ms
009: 5.15978ms
010: 6.191736ms

func (*Exponential) Attempt

func (b *Exponential) Attempt(n int) time.Duration

type ExponentialConfig added in v0.0.2

type ExponentialConfig struct {
	// Offset is a constant duration added to every backoff.
	// Must be >= 0.
	// Zero value (default): no offset is added.
	Offset time.Duration
	// Limit caps the maximum backoff duration.
	// Must be >= Offset.
	// Zero value (default): no upper limit is applied.
	Limit time.Duration
	// Coeff is the coefficient multiplied by Base^n.
	// Must be >= 0.
	// Zero value (default): 1 millisecond.
	Coeff time.Duration
	// Base is the base number applied to the attempt number n.
	// Must be >= 1.
	// Zero value (default): 2.0.
	Base float64
	// Jitter controls how much random fluctuation is added to the
	// backoff duration, in the range [0.0, 1.0]:
	//   - 0.0: no jitter
	//   - 0.5: half jitter
	//   - 1.0: full jitter
	// Zero value (default): 0.0 (no jitter).
	Jitter float64
}

ExponentialConfig is the configuration for Exponential.

Backoff duration for the n-th attempt with no jitter is:

d = offset + coeff * base^n

Each field has a zero-value default as described below, so a ExponentialConfig can be constructed with only the fields that need to differ from the defaults.

func (*ExponentialConfig) New added in v0.0.2

func (c *ExponentialConfig) New() (*Exponential, error)

type Fibonacci

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

Fibonacci provides fibonacci backoff strategy.

Example
package main

import (
	"fmt"
	"time"

	"github.com/aileron-projects/go-backoff"
)

func main() {
	fibonacci := &backoff.FibonacciConfig{
		Offset: 0 * time.Millisecond,
		Limit:  600 * time.Second,
		Coeff:  500 * time.Microsecond,
		Jitter: backoff.NoJitter,
	}

	bo, err := fibonacci.New()
	if err != nil {
		panic(err)
	}

	for i := range 11 {
		d := bo.Attempt(i)
		fmt.Printf("%03d: %s\n", i, d)
	}
}
Output:
000: 0s
001: 500µs
002: 500µs
003: 1ms
004: 1.5ms
005: 2.5ms
006: 4ms
007: 6.5ms
008: 10.5ms
009: 17ms
010: 27.5ms

func (*Fibonacci) Attempt

func (b *Fibonacci) Attempt(n int) time.Duration

type FibonacciConfig added in v0.0.2

type FibonacciConfig struct {
	// Offset is a constant duration added to every backoff.
	// Must be >= 0.
	// Zero value (default): no offset is added.
	Offset time.Duration
	// Limit caps the maximum backoff duration.
	// Must be >= Offset.
	// Zero value (default): no upper limit is applied.
	Limit time.Duration
	// Coeff is the coefficient multiplied by fibonacci(n).
	// Must be >= 0.
	// Zero value (default): 1 millisecond.
	Coeff time.Duration
	// Jitter controls how much random fluctuation is added to the
	// backoff duration, in the range [0.0, 1.0]:
	//   - 0.0: no jitter
	//   - 0.5: half jitter
	//   - 1.0: full jitter
	// Zero value (default): 0.0 (no jitter).
	Jitter float64
}

FibonacciConfig is the configuration for Fibonacci.

Backoff duration for the n-th attempt with no jitter is:

d = offset + coeff * fibonacci(n)

Each field has a zero-value default as described below, so a FibonacciConfig can be constructed with only the fields that need to differ from the defaults.

func (*FibonacciConfig) New added in v0.0.2

func (c *FibonacciConfig) New() (*Fibonacci, error)

type Fixed

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

Fixed provides fixed-interval backoff strategy.

Example
package main

import (
	"fmt"
	"time"

	"github.com/aileron-projects/go-backoff"
)

func main() {
	fixed := &backoff.FixedConfig{
		Interval: 100 * time.Millisecond,
	}

	bo, err := fixed.New()
	if err != nil {
		panic(err)
	}

	for i := range 11 {
		d := bo.Attempt(i)
		fmt.Printf("%03d: %s\n", i, d)
	}
}
Output:
000: 0s
001: 100ms
002: 100ms
003: 100ms
004: 100ms
005: 100ms
006: 100ms
007: 100ms
008: 100ms
009: 100ms
010: 100ms

func (*Fixed) Attempt

func (b *Fixed) Attempt(n int) time.Duration

type FixedConfig added in v0.0.2

type FixedConfig struct {
	// Interval is a constant duration of every backoff.
	// Must be >= 0.
	// Zero value (default): 1 second.
	Interval time.Duration
}

FixedConfig is the configuration for Fixed.

Backoff duration for the n-th attempt is:

d = interval

Each field has a zero-value default as described below, so a FixedConfig can be constructed with only the fields that need to differ from the defaults.

func (*FixedConfig) New added in v0.0.2

func (c *FixedConfig) New() (*Fixed, error)

type Linear

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

Linear provides linear backoff strategy.

Example
package main

import (
	"fmt"
	"time"

	"github.com/aileron-projects/go-backoff"
)

func main() {
	linear := &backoff.LinearConfig{
		Offset: 0 * time.Millisecond,
		Limit:  200 * time.Millisecond,
		Coeff:  5 * time.Millisecond,
		Jitter: backoff.NoJitter,
	}

	bo, err := linear.New()
	if err != nil {
		panic(err)
	}

	for i := range 11 {
		d := bo.Attempt(i)
		fmt.Printf("%03d: %s\n", i, d)
	}
}
Output:
000: 0s
001: 5ms
002: 10ms
003: 15ms
004: 20ms
005: 25ms
006: 30ms
007: 35ms
008: 40ms
009: 45ms
010: 50ms

func (*Linear) Attempt

func (b *Linear) Attempt(n int) time.Duration

type LinearConfig added in v0.0.2

type LinearConfig struct {
	// Offset is a constant duration added to every backoff.
	// Must be >= 0.
	// Zero value (default): no offset is added.
	Offset time.Duration
	// Limit caps the maximum backoff duration.
	// Must be >= Offset.
	// Zero value (default): no upper limit is applied.
	Limit time.Duration
	// Coeff is the coefficient multiplied by n.
	// Must be >= 0.
	// Zero value (default): 1 second.
	Coeff time.Duration
	// Jitter controls how much random fluctuation is added to the
	// backoff duration, in the range [0.0, 1.0]:
	//   - 0.0: no jitter
	//   - 0.5: half jitter
	//   - 1.0: full jitter
	// Zero value (default): 0.0 (no jitter).
	Jitter float64
}

LinearConfig is the configuration for Linear.

Backoff duration for the n-th attempt with no jitter is:

d = offset + coeff * n

Each field has a zero-value default as described below, so a LinearConfig can be constructed with only the fields that need to differ from the defaults.

func (*LinearConfig) New added in v0.0.2

func (c *LinearConfig) New() (*Linear, error)

type Notify

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

Notify reports the outcome of a retryable function to the retryer. The retryer stops retrying when either Stop or Success is called.

func (*Notify) RetryCount added in v0.0.2

func (n *Notify) RetryCount() int

RetryCount returns the count of retry. 0 for first run. n>=1 for n-th retry.

func (*Notify) Stop

func (n *Notify) Stop()

Stop tells the retryer to stop retrying because the operation cannot succeed by retrying. Use [Success] when the operation has completed successfully.

func (*Notify) Success

func (n *Notify) Success()

Success tells the retryer to stop retrying because the operation has completed successfully. Use [Stop] when the operation should stop retrying without success.

type Polynomial

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

Polynomial provides polynomial backoff strategy.

Example
package main

import (
	"fmt"
	"time"

	"github.com/aileron-projects/go-backoff"
)

func main() {
	polynomial := &backoff.PolynomialConfig{
		Offset:   0 * time.Millisecond,
		Limit:    600 * time.Second,
		Coeff:    10 * time.Millisecond,
		Exponent: 2.0,
		Jitter:   backoff.NoJitter,
	}

	bo, err := polynomial.New()
	if err != nil {
		panic(err)
	}

	for i := range 11 {
		d := bo.Attempt(i)
		fmt.Printf("%03d: %s\n", i, d)
	}
}
Output:
000: 0s
001: 10ms
002: 40ms
003: 90ms
004: 160ms
005: 250ms
006: 360ms
007: 490ms
008: 640ms
009: 810ms
010: 1s

func (*Polynomial) Attempt

func (b *Polynomial) Attempt(n int) time.Duration

type PolynomialConfig added in v0.0.2

type PolynomialConfig struct {
	// Offset is a constant duration added to every backoff.
	// Must be >= 0.
	// Zero value (default): no offset is added.
	Offset time.Duration
	// Limit caps the maximum backoff duration.
	// Must be >= Offset.
	// Zero value (default): no upper limit is applied.
	Limit time.Duration
	// Coeff is the coefficient multiplied by n^Exponent.
	// Must be >= 0.
	// Zero value (default): 1 millisecond.
	Coeff time.Duration
	// Exponent is the exponent applied to the attempt number n.
	// Must be >= 0.
	// Zero value (default): 2.0.
	Exponent float64
	// Jitter controls how much random fluctuation is added to the
	// backoff duration, in the range [0.0, 1.0]:
	//   - 0.0: no jitter
	//   - 0.5: half jitter
	//   - 1.0: full jitter
	// Zero value (default): 0.0 (no jitter).
	Jitter float64
}

PolynomialConfig is the configuration for Polynomial.

Backoff duration for the n-th attempt with no jitter is:

d = offset + coeff * n^exponent

Each field has a zero-value default as described below, so a PolynomialConfig can be constructed with only the fields that need to differ from the defaults.

func (*PolynomialConfig) New added in v0.0.2

func (c *PolynomialConfig) New() (*Polynomial, error)

type Random

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

Random provides random backoff strategy.

Example
package main

import (
	"fmt"
	"time"

	"github.com/aileron-projects/go-backoff"
)

func main() {
	random := &backoff.RandomConfig{
		Offset: 0 * time.Millisecond,
		Limit:  100 * time.Millisecond,
	}

	bo, err := random.New()
	if err != nil {
		panic(err)
	}

	for i := range 11 {
		d := bo.Attempt(i)
		fmt.Printf("%03d: %s\n", i, d)
	}
}

func (*Random) Attempt

func (b *Random) Attempt(n int) time.Duration

type RandomConfig added in v0.0.2

type RandomConfig struct {
	// Offset is a constant duration added to every backoff.
	// Must be >= 0.
	// Zero value (default): no offset is added.
	Offset time.Duration
	// Limit caps the maximum backoff duration.
	// Must be >= Offset.
	// Zero value (default): 1 second.
	Limit time.Duration
}

RandomConfig is the configuration for Random.

Backoff duration for the n-th attempt is:

d = offset + random(limit-offset)

Each field has a zero-value default as described below, so a RandomConfig can be constructed with only the fields that need to differ from the defaults.

func (*RandomConfig) New added in v0.0.2

func (c *RandomConfig) New() (*Random, error)

type Retryer

type Retryer struct {

	// MaxRetry specifies the maximum number of retries.
	// The retryable function is executed at most MaxRetry + 1 times.
	// A value of zero or less disables retries.
	MaxRetry int
	// MaxElapsedTime specifies the maximum duration allowed for retries.
	// The duration starts when Run or RunContext is called.
	// It does not cancel a retryable function that is already running.
	MaxElapsedTime time.Duration
	// contains filtered or unexported fields
}

Retryer runs retryable functions with the backoff algorithm.

Example (Stop)
package main

import (
	"errors"
	"fmt"
	"time"

	"github.com/aileron-projects/go-backoff"
)

func main() {
	bo, err := (&backoff.FixedConfig{10 * time.Millisecond}).New()
	if err != nil {
		panic(err)
	}

	r := backoff.NewRetryer(bo)
	r.MaxRetry = 100
	r.MaxElapsedTime = 0 // No timeout

	err = r.Run(func(n *backoff.Notify) error {
		count := n.RetryCount()
		fmt.Printf("%d-th call\n", count)
		if count == 3 {
			n.Stop()
			fmt.Println("stop !!")
			return errors.New("Err-stop")
		}
		return fmt.Errorf("Err-%d", count)
	})

	fmt.Println("----- Error -----")
	fmt.Println(err)
}
Output:
0-th call
1-th call
2-th call
3-th call
stop !!
----- Error -----
Err-0
Err-1
Err-2
Err-stop
Example (Success)
package main

import (
	"fmt"
	"time"

	"github.com/aileron-projects/go-backoff"
)

func main() {
	bo, err := (&backoff.FixedConfig{10 * time.Millisecond}).New()
	if err != nil {
		panic(err)
	}

	r := backoff.NewRetryer(bo)
	r.MaxRetry = 100
	r.MaxElapsedTime = 0 // No timeout

	err = r.Run(func(n *backoff.Notify) error {
		count := n.RetryCount()
		fmt.Printf("%d-th call\n", count)
		if count == 3 {
			n.Success()
			fmt.Println("success !!")
			return nil
		}
		return fmt.Errorf("Err-%d", count)
	})

	fmt.Println("----- Error -----")
	fmt.Println(err)
}
Output:
0-th call
1-th call
2-th call
3-th call
success !!
----- Error -----
<nil>

func NewRetryer

func NewRetryer(backoff Backoff) *Retryer

NewRetryer creates a new Retryer with the given backoff. Returned retryer has no retry and has no timeout. It uses (&RandomConfig{Limit: time.Second}).New() when nil is given.

func (*Retryer) Run

func (r *Retryer) Run(retryable func(*Notify) error) error

Run executes the retryable function using a background context.

func (*Retryer) RunContext

func (r *Retryer) RunContext(ctx context.Context, retryable func(*Notify) error) error

RunContext executes the retryable function with the configured backoff strategy. It returns nil if Notify.Success is called. It returns the errors returned by the retryable function joined with errors.Join if Notify.Stop is called. It returns an error wrapping ctx.Err() and all errors returned so far by the retryable function if ctx is canceled or its deadline is exceeded while waiting for the next attempt. It returns an error wrapping ErrTimeout and all errors returned by the retryable function if MaxElapsedTime is reached. It returns an error wrapping ErrMaxRetry and all errors returned by the retryable function if MaxRetry is reached.

Directories

Path Synopsis
exponential command
fibonacci command
fixed command
linear command
polynomial command
random command
retry command

Jump to

Keyboard shortcuts

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