retry.go (3773B)
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 }