backoff

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2019 License: MIT Imports: 6 Imported by: 0

README

Exponential Backoff in Go

Implements an exponential backoff algorithm. Useful for situations where you want to poll a resource that can intermittently fail (REST API, gRPC, etc) but you do not want to flood the polled service on successive requests.

Synopsis

import (
	"context"
	"time"

	"github.com/rhomel/backoff"
)

func main() {
	bo := backoff.NewBackoff(backoff.DefaultBinaryExponential())

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	err = bo.Try(ctx, 5, func(ctx context.Context) bool {
		APIErr := api.CallThatCanIntermittentlyFail(ctx)
		return APIErr == nil // return true to report success
	})

	if err != nil {
		// all requests failed or context timed out
	}

	// success!
}

In this case, we assume that api.CallThatCanIntermittentlyFail will correctly abort when ctx.Done() channel is closed.

See a working example for more.

Default Binary Exponential

The default retry interval is a binary exponential algorithm with the following progression:

0.5s, 1s, 2s, 4s, 8s, 16s, 20s, 20s, ...

Default Binary Exponential with Jitter

You can add a random "jitter" using DefaultBinaryExponentialJitter. The default jitter will add/subtract a random value in the range of -0.5s to 0.5s. This will help distribute simulataneous failed polls by multiple clients over a short period.

This will see the pseudo random generator with a cryptographically random number so you will get random (non-deterministic) pauses on successive backoff intervals.

interval, err := DefaultBinaryExponentialJitter()
if err != nil {
	// error likely due to crypto/rand io error
}
bo := backoff.NewBackoff(interval)

Custom Exponential Series

You can configure the Exponential parameters to something that suits your application:

e := Exponential{
	Base:    3 * time.Second,
	Unit:    time.Second,
	Initial: 1 * time.Second,
	Max:     30 * time.Second,
}
bo := backoff.NewBackoff(e)

produces a backoff series:

1s, 3s, 9s, 27s, 30s, 30s, ...

Custom Interval Implementations

You can also provide your own backoff interval implementation by satisfying the Intervals interface. For example this is a very naive custom binary exponential implementation to demonstrate:

type Custom struct {}

func (c Custom) Next(i int8, last time.Duration) time.Duration {
	if last == 0 {
		return time.Second
	}
	return last * 2
	// warning: chance the return can overflow
}

// use the custom interval later
func main() {
	bo := backoff.NewBackoff(Custom{})
}

The Backoff.Try method will call your Next method starting with i=0 and last=0 on the first iteration. i will continue to increment by one up to math.MaxInt8. So it is safe to assume i will be in the range 0 to math.MaxInt8.

Even if you fail, don't give up... (infinite tries)

You can configure Try to try forever:

err := bo.Try(ctx, backoff.InfiniteTries, func(ctx context.Context) bool {
	// your code
})

Obviously if your Completable func never returns true then this will try forever.

Caution

Don't provide a non-cancellable Context

While you can provide a Context without a timeout or deadline with something like context.Background it will create a possibility that Try will block forever even if tries is finite. For this reason the Synopsis purposely includes a context with a timeout. An example where this can happen is using the http.DefaultClient without a timeout.

Similarly, in your provided Completable func, you should take care to listen to the ctx.Done() channel when implementing your own routine. If the called routine does properly support Context then you do not need to take action.

Documentation

Index

Constants

View Source
const (
	// InfiniteTries represents infinite `tries`. Use this in the `Try` method to
	// keep trying until Completable returns true
	InfiniteTries = math.MaxInt8

	// AllTriesFailed indicates that all requested tries failed
	AllTriesFailed = Error("all tries failed")
	// BackoffContextTimeoutExceeded indicates that the backoff context Done
	// channel was closed
	BackoffContextTimeoutExceeded = Error("backoff context timeout exceeded")
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Backoff

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

Backoff is a simple backoff implementation. You will want to use NewBackoff or NewBackoffWithTimeout to create an instance.

func NewBackoff

func NewBackoff(intervals Intervals, options ...Options) *Backoff

NewBackoff creates a new Backoff struct. Intervals represents the interval calculation algorithm (ex: Exponential). Tries represents the number of times to call the function. Context may be a cancellable context.

If you want a timeout Context, consider using NewBackoffWithTimeout instead.

func (*Backoff) Try

func (b *Backoff) Try(ctx context.Context, tries int8, fn Completable) error

Try will try to call the provided Completable the number of times specifed in NewBackoff until an execution of Completable returns true.

If the Completable returns false more times than specified in tries in NewBackoff, then Try will return a AllTriesFailed error.

If the provided context cancel function is called before a Completable call returns true, then Try will return a BackoffContextTimeoutExceeded error.

type Completable

type Completable func(ctx context.Context) bool

Completable is a function that should complete and terminate early if the context.Done() channel is closed.

type Error

type Error string

Error represents a constant typed error

func (Error) Error

func (e Error) Error() string

type Exponential

type Exponential struct {
	Base    time.Duration
	Unit    time.Duration
	Initial time.Duration
	Max     time.Duration
}

Exponential implements an exponential interval function.

func DefaultBinaryExponential

func DefaultBinaryExponential() Exponential

DefaultBinaryExponential creates a binary exponential interval function with the following series: 0.5s, 1s, 2s, 4s, 8s, 16s, 20s, 20s, ...

func (Exponential) Next

func (e Exponential) Next(i int8, last time.Duration) time.Duration

Next provides the interval in the series based in iteration.

Note that we intentially do not use `last` in this function so it is easy to add a consistent Jitter implementation on top of this. The trade-off is we have to do a floating point Pow calculation.

type ExponentialJitter

type ExponentialJitter struct {
	Exponential
	JitterMax time.Duration
	Rand      *rand.Rand
}

ExponentialJitter implements an exponential interval function with a random jitter factor added to each fixed interval.

func DefaultBinaryExponentialJitter

func DefaultBinaryExponentialJitter() (ExponentialJitter, error)

DefaultBinaryExponentialJitter creates a DefaultBinaryExponential interval function with each interval adjusted by a random value between +/- 500ms. The current underlying implemntation uses crypto/rand to seed the psuedo-random generator.

Since the crypt/rand generator can fail due to io errors, the method returns an error if any.

func (ExponentialJitter) Next

func (ej ExponentialJitter) Next(i int8, last time.Duration) time.Duration

Next provides the interval in the series based in iteration. Since this method contains jitter and it is seeded by crypto/rand it will return seemingly non-deterministic random values.

type Intervals

type Intervals interface {
	Next(i int8, last time.Duration) time.Duration
}

Intervals represents the interface backoff interval function should implement. `i` represents the current iteration. `last` represents the last backoff duration for the previous iteration, zero if this is the first iteration. The number of iterations is expected to be fairly small, but if the number of iterations is InfiniteTries (math.MaxInt8), `i` will always be InfiniteTries.

type Options

type Options func(bo *Backoff)

Options are additional options to be used in NewBackoff. Currently there are no exported options, only options that are used internally for testing.

Directories

Path Synopsis
test
try

Jump to

Keyboard shortcuts

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