retry.go 3.7 KB

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