github.com/cenkalti/backoff/v4@v4.2.1/retry.go (about)

     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  }