Skip to content
/ retry Public

The retry package encapsulates the mechanism around retrying commands.

License

Notifications You must be signed in to change notification settings

juju/retry

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

retry

import "github.com/juju/retry"

The retry package encapsulates the mechanism around retrying commands.

The simple use is to call retry.Call with a function closure.

	err := retry.Call(retry.CallArgs{
		Func:     func() error { ... },
		Attempts: 5,
		Delay:    time.Minute,
		Clock:    clock.WallClock,
	})

The bare minimum arguments that need to be specified are:

  • Func - the function to call
  • Attempts - the number of times to try Func before giving up, or a negative number for unlimited attempts (retry.UnlimitedAttempts)
  • Delay - how long to wait between each try that returns an error
  • Clock - either the wall clock, or some testing clock

Any error that is returned from the Func is considered transient. In order to identify some errors as fatal, pass in a function for the IsFatalError CallArgs value.

In order to have the Delay change for each iteration, a BackoffFunc needs to be set on the CallArgs. A simple doubling delay function is provided by DoubleDelay.

An example of a more complex BackoffFunc could be a stepped function such as:

	func StepDelay(last time.Duration, attempt int) time.Duration {
		switch attempt{
		case 1:
			return time.Second
		case 2:
			return 5 * time.Second
		case 3:
			return 20 * time.Second
		case 4:
			return time.Minute
		case 5:
			return 5 * time.Minute
		default:
			return 2 * last
		}
	}

Consider some package foo that has a TryAgainError, which looks something like this:

	type TryAgainError struct {
		After time.Duration
	}

and we create something that looks like this:

	type TryAgainHelper struct {
		next time.Duration
	}
	
	func (h *TryAgainHelper) notify(lastError error, attempt int) {
		if tryAgain, ok := lastError.(*foo.TryAgainError); ok {
			h.next = tryAgain.After
		} else {
			h.next = 0
		}
	}
	
	func (h *TryAgainHelper) next(last time.Duration) time.Duration {
		if h.next != 0 {
			return h.next
		}
		return last
	}

Then we could do this:

	helper := TryAgainHelper{}
	retry.Call(retry.CallArgs{
		Func: func() error {
			return foo.SomeFunc()
		},
		NotifyFunc:  helper.notify,
		BackoffFunc: helper.next,
		Attempts:    20,
		Delay:       100 * time.Millisecond,
		Clock:       clock.WallClock,
	})

Constants

const (
    // UnlimitedAttempts can be used as a value for `Attempts` to clearly
    // show to the reader that there is no limit to the number of attempts.
    UnlimitedAttempts = -1
)

func Call

func Call(args CallArgs) error

Call will repeatedly execute the Func until either the function returns no error, the retry count is exceeded or the stop channel is closed.

func DoubleDelay

func DoubleDelay(delay time.Duration, attempt int) time.Duration

DoubleDelay provides a simple function that doubles the duration passed in. This can then be easily used as the BackoffFunc in the CallArgs structure.

func ExpBackoff

func ExpBackoff(minDelay, maxDelay time.Duration, exp float64, applyJitter bool) func(time.Duration, int) time.Duration {

ExpBackoff returns a function a which generates time.Duration values using an exponential back-off algorithm with the specified parameters. The returned value can then be easily used as the BackoffFunc in the CallArgs structure.

The next delay value is calculated using the following formula: newDelay = min(minDelay * exp^attempt, maxDelay)

If applyJitter is set to true, the function will randomly select and return back a value in the [minDelay, newDelay] range.

func IsAttemptsExceeded

func IsAttemptsExceeded(err error) bool

IsAttemptsExceeded returns true if the error is the result of the Call function finishing due to hitting the requested number of Attempts.

func IsDurationExceeded

func IsDurationExceeded(err error) bool

IsDurationExceeded returns true if the error is the result of the Call function finishing due to the total duration exceeding the specified MaxDuration value.

func IsRetryStopped

func IsRetryStopped(err error) bool

IsRetryStopped returns true if the error is the result of the Call function finishing due to the stop channel being closed.

func LastError

func LastError(err error) error

LastError retrieves the last error returned from Func before iteration was terminated due to the attempt count being exceeded, the maximum duration being exceeded, or the stop channel being closed.

type CallArgs

type CallArgs struct {
    // Func is the function that will be retried if it returns an error result.
    Func func() error

    // IsFatalError is a function that, if set, will be called for every non-
    // nil error result from `Func`. If `IsFatalError` returns true, the error
    // is immediately returned breaking out from any further retries.
    IsFatalError func(error) bool

    // NotifyFunc is a function that is called if Func fails, and the attempt
    // number. The first time this function is called attempt is 1, the second
    // time, attempt is 2 and so on.
    NotifyFunc func(lastError error, attempt int)

    // Attempts specifies the number of times Func should be retried before
    // giving up and returning the `AttemptsExceeded` error. If a negative
    // value is specified, the `Call` will retry forever.
    Attempts int

    // Delay specifies how long to wait between retries.
    Delay time.Duration

    // MaxDelay specifies how longest time to wait between retries. If no
    // value is specified there is no maximum delay.
    MaxDelay time.Duration

    // MaxDuration specifies the maximum time the `Call` function should spend
    // iterating over `Func`. The duration is calculated from the start of the
    // `Call` function.  If the next delay time would take the total duration
    // of the call over MaxDuration, then a DurationExceeded error is
    // returned. If no value is specified, Call will continue until the number
    // of attempts is complete.
    MaxDuration time.Duration

    // BackoffFunc allows the caller to provide a function that alters the
    // delay each time through the loop. If this function is not provided the
    // delay is the same each iteration. Alternatively a function such as
    // `retry.DoubleDelay` can be used that will provide an exponential
    // backoff. The first time this function is called attempt is 1, the
    // second time, attempt is 2 and so on.
    BackoffFunc func(delay time.Duration, attempt int) time.Duration

    // Clock provides the mechanism for waiting. Normal program execution is
    // expected to use something like clock.WallClock, and tests can override
    // this to not actually sleep in tests.
    Clock clock.Clock

    // Stop is a channel that can be used to indicate that the waiting should
    // be interrupted. If Stop is nil, then the Call function cannot be interrupted.
    // If the channel is closed prior to the Call function being executed, the
    // Func is still attempted once.
    Stop <-chan struct{}
}

CallArgs is a simple structure used to define the behaviour of the Call function.

func (*CallArgs) Validate

func (args *CallArgs) Validate() error

Validate the values are valid. The ensures that the Func, Delay, Attempts and Clock have been specified.


Generated by godoc2md