1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the LGPLv3, see LICENCE file for details.
10
"github.com/juju/errors"
11
"github.com/juju/utils/clock"
15
// UnlimitedAttempts can be used as a value for `Attempts` to clearly
16
// show to the reader that there is no limit to the number of attempts.
17
UnlimitedAttempts = -1
20
// retryStopped is the error that is returned from the `Call` function
21
// when the stop channel has been closed.
22
type retryStopped struct {
26
// Error provides the implementation for the error interface method.
27
func (e *retryStopped) Error() string {
28
return fmt.Sprintf("retry stopped")
31
// attemptsExceeded is the error that is returned when the retry count has
32
// been hit without the function returning a nil error result. The last error
33
// returned from the function being retried is available as the LastError
35
type attemptsExceeded struct {
39
// Error provides the implementation for the error interface method.
40
func (e *attemptsExceeded) Error() string {
41
return fmt.Sprintf("attempt count exceeded: %s", e.lastError)
44
// durationExceeded is the error that is returned when the total time that the
45
// `Call` function would have executed exceeds the `MaxDuration` specified.
46
// The last error returned from the function being retried is available as the
47
// LastError attribute.
48
type durationExceeded struct {
52
// Error provides the implementation for the error interface method.
53
func (e *durationExceeded) Error() string {
54
return fmt.Sprintf("max duration exceeded: %s", e.lastError)
57
// LastError retrieves the last error returned from `Func` before iteration
58
// was terminated due to the attempt count being exceeded, the maximum
59
// duration being exceeded, or the stop channel being closed.
60
func LastError(err error) error {
61
cause := errors.Cause(err)
62
switch err := cause.(type) {
63
case *attemptsExceeded:
67
case *durationExceeded:
70
return errors.Errorf("unexpected error type: %T, %s", cause, cause)
73
// IsAttemptsExceeded returns true if the error is the result of the `Call`
74
// function finishing due to hitting the requested number of `Attempts`.
75
func IsAttemptsExceeded(err error) bool {
76
cause := errors.Cause(err)
77
_, ok := cause.(*attemptsExceeded)
81
// IsDurationExceeded returns true if the error is the result of the `Call`
82
// function finishing due to the total duration exceeding the specified
83
// `MaxDuration` value.
84
func IsDurationExceeded(err error) bool {
85
cause := errors.Cause(err)
86
_, ok := cause.(*durationExceeded)
90
// IsRetryStopped returns true if the error is the result of the `Call`
91
// function finishing due to the stop channel being closed.
92
func IsRetryStopped(err error) bool {
93
cause := errors.Cause(err)
94
_, ok := cause.(*retryStopped)
98
// CallArgs is a simple structure used to define the behaviour of the Call
100
type CallArgs struct {
101
// Func is the function that will be retried if it returns an error result.
104
// IsFatalError is a function that, if set, will be called for every non-
105
// nil error result from `Func`. If `IsFatalError` returns true, the error
106
// is immediately returned breaking out from any further retries.
107
IsFatalError func(error) bool
109
// NotifyFunc is a function that is called if Func fails, and the attempt
110
// number. The first time this function is called attempt is 1, the second
111
// time, attempt is 2 and so on.
112
NotifyFunc func(lastError error, attempt int)
114
// Attempts specifies the number of times Func should be retried before
115
// giving up and returning the `AttemptsExceeded` error. If a negative
116
// value is specified, the `Call` will retry forever.
119
// Delay specifies how long to wait between retries.
122
// MaxDelay specifies how longest time to wait between retries. If no
123
// value is specified there is no maximum delay.
124
MaxDelay time.Duration
126
// MaxDuration specifies the maximum time the `Call` function should spend
127
// iterating over `Func`. The duration is calculated from the start of the
128
// `Call` function. If the next delay time would take the total duration
129
// of the call over MaxDuration, then a DurationExceeded error is
130
// returned. If no value is specified, Call will continue until the number
131
// of attempts is complete.
132
MaxDuration time.Duration
134
// BackoffFunc allows the caller to provide a function that alters the
135
// delay each time through the loop. If this function is not provided the
136
// delay is the same each iteration. Alternatively a function such as
137
// `retry.DoubleDelay` can be used that will provide an exponential
138
// backoff. The first time this function is called attempt is 1, the
139
// second time, attempt is 2 and so on.
140
BackoffFunc func(delay time.Duration, attempt int) time.Duration
142
// Clock provides the mechanism for waiting. Normal program execution is
143
// expected to use something like clock.WallClock, and tests can override
144
// this to not actually sleep in tests.
147
// Stop is a channel that can be used to indicate that the waiting should
148
// be interrupted. If Stop is nil, then the Call function cannot be interrupted.
149
// If the channel is closed prior to the Call function being executed, the
150
// Func is still attempted once.
154
// Validate the values are valid. The ensures that the Func, Delay, Attempts
155
// and Clock have been specified.
156
func (args *CallArgs) Validate() error {
157
if args.Func == nil {
158
return errors.NotValidf("missing Func")
161
return errors.NotValidf("missing Delay")
163
if args.Clock == nil {
164
return errors.NotValidf("missing Clock")
166
// One of Attempts or MaxDuration need to be specified
167
if args.Attempts == 0 && args.MaxDuration == 0 {
168
return errors.NotValidf("missing Attempts or MaxDuration")
173
// Call will repeatedly execute the Func until either the function returns no
174
// error, the retry count is exceeded or the stop channel is closed.
175
func Call(args CallArgs) error {
176
err := args.Validate()
178
return errors.Trace(err)
180
start := args.Clock.Now()
181
for i := 1; args.Attempts <= 0 || i <= args.Attempts; i++ {
186
if args.IsFatalError != nil && args.IsFatalError(err) {
187
return errors.Trace(err)
189
if args.NotifyFunc != nil {
190
args.NotifyFunc(err, i)
192
if i == args.Attempts && args.Attempts > 0 {
193
break // don't wait before returning the error
196
if args.BackoffFunc != nil {
197
delay := args.BackoffFunc(args.Delay, i)
198
if delay > args.MaxDelay && args.MaxDelay > 0 {
199
delay = args.MaxDelay
203
elapsedTime := args.Clock.Now().Sub(start)
204
if args.MaxDuration > 0 && (elapsedTime+args.Delay) > args.MaxDuration {
205
return errors.Wrap(err, &durationExceeded{err})
208
// Wait for the delay, and retry
210
case <-args.Clock.After(args.Delay):
212
return errors.Wrap(err, &retryStopped{err})
215
return errors.Wrap(err, &attemptsExceeded{err})
218
// DoubleDelay provides a simple function that doubles the duration passed in.
219
// This can then be easily used as the `BackoffFunc` in the `CallArgs`
221
func DoubleDelay(delay time.Duration, attempt int) time.Duration {