blob: b9c0c51cd755f543a9596384e5b8668651204f5c [file] [log] [blame]
Abhay Kumara2ae5992025-11-10 14:02:24 +00001package backoff
2
3import (
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.
10type 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.
14type Operation func() error
15
16func (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.
27type 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.
37func 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.
42func 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.
48func 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.
53func 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.
60func 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.
66func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
67 return doRetryNotify(operation, b, notify, t)
68}
69
70func 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.
121type PermanentError struct {
122 Err error
123}
124
125func (e *PermanentError) Error() string {
126 return e.Err.Error()
127}
128
129func (e *PermanentError) Unwrap() error {
130 return e.Err
131}
132
133func (e *PermanentError) Is(target error) bool {
134 _, ok := target.(*PermanentError)
135 return ok
136}
137
138// Permanent wraps the given err in a *PermanentError.
139func Permanent(err error) error {
140 if err == nil {
141 return nil
142 }
143 return &PermanentError{
144 Err: err,
145 }
146}