Retry
Retry enables attempting an operation multiple times, stopping on success (no error returned) or permanent operation failure.
Retrying
To retry an action, create a new instance of Retry via NewRetry and
call its Retry method:
tooEarly := time.Parse("2020-01-01T00:00:00Z")
tooLate := time.Parse("2020-12-31T23:59:59.999999999Z")
// Retry once an hour
r := NewRetry(ctx, RetryConfig{Attempts:10, Delay: 60 * 60 * 1000})
err := r.Retry(func() error {
now := time.Now()
if now.Before(tooEarly) {
return retry.TransientError{
Cause: errors.New("Will succeed in the future")
}
} else if now.After(tooLate) {
return retry.PermanentError{
Cause: errors.New("Will never succeed again")
}
}
return nil
})
The above retries its action once per hour, with up to 10 attempts.
If the time is before tooEarly, it will continue retrying, since it
returns a TransientError. If the time is after tooLate, it will
stop retrying, since it returns PermanentError. If the time is after tooEarly
but before tooLate, it will succeed and cease further attempts.
Retry distinguishes between Transient and Permanent errors by inspecting
the returned error instance. If it implements the failure interface,
it can be queried for transience/permanence:
type failure interface {
IsPermanent() bool
}
Permanent errors should return true from IsPermanent(), transient
errors should return false. As above, this can be handled by wrapping
the error in either PermanentError or TransientError.
Configuration Loading
RetryConfig is designed to be loaded from configuration, making it possible
to configure from static, environmental, or remote configuration
sources in a consistent fashion.
const configRootMerakiClientRetry = "meraki.client.retry"
var retryConfig retry.Config
if err := config.FromContext(ctx).Populate(&retryConfig, configRootMerakiClientRetry); err != nil {
return err
}
Configuration Examples
-
Retries without delays
r := NewRetry(ctx, RetryConfig{ Attempts: 2, Delay: 0, BackOff: 0.0, Linear: true, }) -
Retries with fixed delays (1 second)
r := NewRetry(ctx, RetryConfig{ Attempts: 2, Delay: 1000, BackOff: 1.0, Linear: true, }) -
Retries with linear delays (1, 2, 3, 4)
r := NewRetry(ctx, RetryConfig{ Attempts: 5, Delay: 1000, BackOff: 1.0, Linear: true, }) -
Retries with exponential delays (1, 2, 4, 8)
r := NewRetry(ctx, RetryConfig{ Attempts: 5, Delay: 1000, BackOff: 2.0, Linear: false, }) -
Retries with linear delay and Jitter (low random) (1, 2.452, 3.571, 4.357)
r := NewRetry(ctx, RetryConfig{ Attempts: 5, Delay: 1000, BackOff: 1.0, Linear: true, Jitter: 1000, }) -
Retries with linear delay and Jitter (extreme random) (1, 7.8, 20.3, 8.45)
With higher Jitter value you could expect greater randomness.r := NewRetry(ctx, RetryConfig{ Attempts: 5, Delay: 1000, BackOff: 1.0, Linear: true, Jitter: 20000, }) -
Retries with exponential delay and Jitter (1, 2, 4, 8) (note: jitter is negligible so this is just like exponential backoff with no jitter)
r := NewRetry(ctx, RetryConfig{ Attempts: 5, Delay: 1000, BackOff: 2.0, Linear: false, Jitter: 1, }) -
Using retry with decorator
types. NewOperation(func(ctx context.Context) error { return errors.New("a transient error") }). WithDecorator(Decorator(RetryConfig{ Attempts: 1, Delay: 10, BackOff: 2.0, Linear: false, Jitter: 1, })). Run(ctx)