~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/retry/retry.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 Canonical Ltd.
 
2
// Licensed under the LGPLv3, see LICENCE file for details.
 
3
 
 
4
package retry
 
5
 
 
6
import (
 
7
        "fmt"
 
8
        "time"
 
9
 
 
10
        "github.com/juju/errors"
 
11
        "github.com/juju/utils/clock"
 
12
)
 
13
 
 
14
const (
 
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
 
18
)
 
19
 
 
20
// retryStopped is the error that is returned from the `Call` function
 
21
// when the stop channel has been closed.
 
22
type retryStopped struct {
 
23
        lastError error
 
24
}
 
25
 
 
26
// Error provides the implementation for the error interface method.
 
27
func (e *retryStopped) Error() string {
 
28
        return fmt.Sprintf("retry stopped")
 
29
}
 
30
 
 
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
 
34
// attribute.
 
35
type attemptsExceeded struct {
 
36
        lastError error
 
37
}
 
38
 
 
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)
 
42
}
 
43
 
 
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 {
 
49
        lastError error
 
50
}
 
51
 
 
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)
 
55
}
 
56
 
 
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:
 
64
                return err.lastError
 
65
        case *retryStopped:
 
66
                return err.lastError
 
67
        case *durationExceeded:
 
68
                return err.lastError
 
69
        }
 
70
        return errors.Errorf("unexpected error type: %T, %s", cause, cause)
 
71
}
 
72
 
 
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)
 
78
        return ok
 
79
}
 
80
 
 
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)
 
87
        return ok
 
88
}
 
89
 
 
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)
 
95
        return ok
 
96
}
 
97
 
 
98
// CallArgs is a simple structure used to define the behaviour of the Call
 
99
// function.
 
100
type CallArgs struct {
 
101
        // Func is the function that will be retried if it returns an error result.
 
102
        Func func() error
 
103
 
 
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
 
108
 
 
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)
 
113
 
 
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.
 
117
        Attempts int
 
118
 
 
119
        // Delay specifies how long to wait between retries.
 
120
        Delay time.Duration
 
121
 
 
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
 
125
 
 
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
 
133
 
 
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
 
141
 
 
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.
 
145
        Clock clock.Clock
 
146
 
 
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.
 
151
        Stop <-chan struct{}
 
152
}
 
153
 
 
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")
 
159
        }
 
160
        if args.Delay == 0 {
 
161
                return errors.NotValidf("missing Delay")
 
162
        }
 
163
        if args.Clock == nil {
 
164
                return errors.NotValidf("missing Clock")
 
165
        }
 
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")
 
169
        }
 
170
        return nil
 
171
}
 
172
 
 
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()
 
177
        if err != nil {
 
178
                return errors.Trace(err)
 
179
        }
 
180
        start := args.Clock.Now()
 
181
        for i := 1; args.Attempts <= 0 || i <= args.Attempts; i++ {
 
182
                err = args.Func()
 
183
                if err == nil {
 
184
                        return nil
 
185
                }
 
186
                if args.IsFatalError != nil && args.IsFatalError(err) {
 
187
                        return errors.Trace(err)
 
188
                }
 
189
                if args.NotifyFunc != nil {
 
190
                        args.NotifyFunc(err, i)
 
191
                }
 
192
                if i == args.Attempts && args.Attempts > 0 {
 
193
                        break // don't wait before returning the error
 
194
                }
 
195
 
 
196
                if args.BackoffFunc != nil {
 
197
                        delay := args.BackoffFunc(args.Delay, i)
 
198
                        if delay > args.MaxDelay && args.MaxDelay > 0 {
 
199
                                delay = args.MaxDelay
 
200
                        }
 
201
                        args.Delay = delay
 
202
                }
 
203
                elapsedTime := args.Clock.Now().Sub(start)
 
204
                if args.MaxDuration > 0 && (elapsedTime+args.Delay) > args.MaxDuration {
 
205
                        return errors.Wrap(err, &durationExceeded{err})
 
206
                }
 
207
 
 
208
                // Wait for the delay, and retry
 
209
                select {
 
210
                case <-args.Clock.After(args.Delay):
 
211
                case <-args.Stop:
 
212
                        return errors.Wrap(err, &retryStopped{err})
 
213
                }
 
214
        }
 
215
        return errors.Wrap(err, &attemptsExceeded{err})
 
216
}
 
217
 
 
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`
 
220
// structure.
 
221
func DoubleDelay(delay time.Duration, attempt int) time.Duration {
 
222
        if attempt == 1 {
 
223
                return delay
 
224
        }
 
225
        return delay * 2
 
226
}