1 package backoff
2 3 import (
4 "errors"
5 "time"
6 )
7 8 // An OperationWithData is executing by RetryWithData() or RetryNotifyWithData().
9 // The operation will be retried using a backoff policy if it returns an error.
10 type OperationWithData[T any] func() (T, error)
11 12 // An Operation is executing by Retry() or RetryNotify().
13 // The operation will be retried using a backoff policy if it returns an error.
14 type Operation func() error
15 16 func (o Operation) withEmptyData() OperationWithData[struct{}] {
17 return func() (struct{}, error) {
18 return struct{}{}, o()
19 }
20 }
21 22 // Notify is a notify-on-error function. It receives an operation error and
23 // backoff delay if the operation failed (with an error).
24 //
25 // NOTE that if the backoff policy stated to stop retrying,
26 // the notify function isn't called.
27 type Notify func(error, time.Duration)
28 29 // Retry the operation o until it does not return error or BackOff stops.
30 // o is guaranteed to be run at least once.
31 //
32 // If o returns a *PermanentError, the operation is not retried, and the
33 // wrapped error is returned.
34 //
35 // Retry sleeps the goroutine for the duration returned by BackOff after a
36 // failed operation returns.
37 func Retry(o Operation, b BackOff) error {
38 return RetryNotify(o, b, nil)
39 }
40 41 // RetryWithData is like Retry but returns data in the response too.
42 func RetryWithData[T any](o OperationWithData[T], b BackOff) (T, error) {
43 return RetryNotifyWithData(o, b, nil)
44 }
45 46 // RetryNotify calls notify function with the error and wait duration
47 // for each failed attempt before sleep.
48 func RetryNotify(operation Operation, b BackOff, notify Notify) error {
49 return RetryNotifyWithTimer(operation, b, notify, nil)
50 }
51 52 // RetryNotifyWithData is like RetryNotify but returns data in the response too.
53 func RetryNotifyWithData[T any](operation OperationWithData[T], b BackOff, notify Notify) (T, error) {
54 return doRetryNotify(operation, b, notify, nil)
55 }
56 57 // RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
58 // for each failed attempt before sleep.
59 // A default timer that uses system timer is used when nil is passed.
60 func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
61 _, err := doRetryNotify(operation.withEmptyData(), b, notify, t)
62 return err
63 }
64 65 // RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too.
66 func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
67 return doRetryNotify(operation, b, notify, t)
68 }
69 70 func doRetryNotify[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
71 var (
72 err error
73 next time.Duration
74 res T
75 )
76 if t == nil {
77 t = &defaultTimer{}
78 }
79 80 defer func() {
81 t.Stop()
82 }()
83 84 ctx := getContext(b)
85 86 b.Reset()
87 for {
88 res, err = operation()
89 if err == nil {
90 return res, nil
91 }
92 93 var permanent *PermanentError
94 if errors.As(err, &permanent) {
95 return res, permanent.Err
96 }
97 98 if next = b.NextBackOff(); next == Stop {
99 if cerr := ctx.Err(); cerr != nil {
100 return res, cerr
101 }
102 103 return res, err
104 }
105 106 if notify != nil {
107 notify(err, next)
108 }
109 110 t.Start(next)
111 112 select {
113 case <-ctx.Done():
114 return res, ctx.Err()
115 case <-t.C():
116 }
117 }
118 }
119 120 // PermanentError signals that the operation should not be retried.
121 type PermanentError struct {
122 Err error
123 }
124 125 func (e *PermanentError) Error() string {
126 return e.Err.Error()
127 }
128 129 func (e *PermanentError) Unwrap() error {
130 return e.Err
131 }
132 133 func (e *PermanentError) Is(target error) bool {
134 _, ok := target.(*PermanentError)
135 return ok
136 }
137 138 // Permanent wraps the given err in a *PermanentError.
139 func Permanent(err error) error {
140 if err == nil {
141 return nil
142 }
143 return &PermanentError{
144 Err: err,
145 }
146 }
147