retry.go raw

   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