gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }