blob: 6c776ccf8ed74e851d28922edf16122f162f70c5 [file] [log] [blame]
khenaidoo5e4fca32021-05-12 16:02:23 -04001package 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 {
25 return RetryNotify(o, b, nil)
26}
27
28// RetryNotify calls notify function with the error and wait duration
29// for each failed attempt before sleep.
30func RetryNotify(operation Operation, b BackOff, notify Notify) error {
31 return RetryNotifyWithTimer(operation, b, notify, nil)
32}
33
34// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
35// for each failed attempt before sleep.
36// A default timer that uses system timer is used when nil is passed.
37func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
38 var err error
39 var next time.Duration
40 if t == nil {
41 t = &defaultTimer{}
42 }
43
44 defer func() {
45 t.Stop()
46 }()
47
48 ctx := getContext(b)
49
50 b.Reset()
51 for {
52 if err = operation(); err == nil {
53 return nil
54 }
55
56 if permanent, ok := err.(*PermanentError); ok {
57 return permanent.Err
58 }
59
60 if next = b.NextBackOff(); next == Stop {
61 return err
62 }
63
64 if notify != nil {
65 notify(err, next)
66 }
67
68 t.Start(next)
69
70 select {
71 case <-ctx.Done():
72 return ctx.Err()
73 case <-t.C():
74 }
75 }
76}
77
78// PermanentError signals that the operation should not be retried.
79type PermanentError struct {
80 Err error
81}
82
83func (e *PermanentError) Error() string {
84 return e.Err.Error()
85}
86
87func (e *PermanentError) Unwrap() error {
88 return e.Err
89}
90
91// Permanent wraps the given err in a *PermanentError.
92func Permanent(err error) *PermanentError {
93 return &PermanentError{
94 Err: err,
95 }
96}