blob: e4c6c9c839a3accbb00b831d0fa78d5578ddc676 [file] [log] [blame]
David Bainbridgef5879ca2019-12-13 21:17:54 +00001package backoff
2
3import "time"
4
5// An Operation is executing by Retry() or RetryNotify().
6// The operation will be retried using a backoff policy if it returns an error.
7type Operation func() error
8
9// Notify is a notify-on-error function. It receives an operation error and
10// backoff delay if the operation failed (with an error).
11//
12// NOTE that if the backoff policy stated to stop retrying,
13// the notify function isn't called.
14type Notify func(error, time.Duration)
15
16// Retry the operation o until it does not return error or BackOff stops.
17// o is guaranteed to be run at least once.
18//
19// If o returns a *PermanentError, the operation is not retried, and the
20// wrapped error is returned.
21//
22// Retry sleeps the goroutine for the duration returned by BackOff after a
23// failed operation returns.
24func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) }
25
26// RetryNotify calls notify function with the error and wait duration
27// for each failed attempt before sleep.
28func RetryNotify(operation Operation, b BackOff, notify Notify) error {
29 var err error
30 var next time.Duration
31 var t *time.Timer
32
33 cb := ensureContext(b)
34
35 b.Reset()
36 for {
37 if err = operation(); err == nil {
38 return nil
39 }
40
41 if permanent, ok := err.(*PermanentError); ok {
42 return permanent.Err
43 }
44
45 if next = cb.NextBackOff(); next == Stop {
46 return err
47 }
48
49 if notify != nil {
50 notify(err, next)
51 }
52
53 if t == nil {
54 t = time.NewTimer(next)
55 defer t.Stop()
56 } else {
57 t.Reset(next)
58 }
59
60 select {
61 case <-cb.Context().Done():
62 return err
63 case <-t.C:
64 }
65 }
66}
67
68// PermanentError signals that the operation should not be retried.
69type PermanentError struct {
70 Err error
71}
72
73func (e *PermanentError) Error() string {
74 return e.Err.Error()
75}
76
77func (e *PermanentError) Unwrap() error {
78 return e.Err
79}
80
81// Permanent wraps the given err in a *PermanentError.
82func Permanent(err error) *PermanentError {
83 return &PermanentError{
84 Err: err,
85 }
86}