github.com/diadata-org/diadata@v1.4.593/pkg/dia/helpers/substrate-helper/gsrpc/registry/exec/exec.go (about) 1 package exec 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "time" 8 ) 9 10 //go:generate mockery --name RetryableExecutor --structname RetryableExecutorMock --filename exec_mock.go --inpackage 11 12 // RetryableExecutor is the interface used for executing a closure and its fallback if the initial execution fails. 13 // 14 // The interface is generic over type T which represents the return value of the closure. 15 type RetryableExecutor[T any] interface { 16 ExecWithFallback(execFn func() (T, error), fallbackFn func() error) (T, error) 17 } 18 19 // retryableExecutor implements RetryableExecutor. 20 // 21 // It can be configured via the provided OptsFn(s). 22 type retryableExecutor[T any] struct { 23 opts *Opts 24 } 25 26 // NewRetryableExecutor creates a new RetryableExecutor. 27 func NewRetryableExecutor[T any](opts ...OptsFn) RetryableExecutor[T] { 28 execOpts := NewDefaultExecOpts() 29 30 for _, opt := range opts { 31 opt(execOpts) 32 } 33 34 return &retryableExecutor[T]{ 35 execOpts, 36 } 37 } 38 39 // ExecWithFallback will attempt to execute the provided execFn and, in the case of failure, it will execute 40 // the fallbackFn and retry execution of execFn. 41 func (r *retryableExecutor[T]) ExecWithFallback(execFn func() (T, error), fallbackFn func() error) (res T, err error) { 42 if execFn == nil { 43 return res, ErrMissingExecFn 44 } 45 46 if fallbackFn == nil { 47 return res, ErrMissingFallbackFn 48 } 49 50 execErr := &Error{} 51 52 retryCount := uint(0) 53 54 for { 55 res, err = execFn() 56 57 if err == nil { 58 return res, nil 59 } 60 61 execErr.AddErr(fmt.Errorf("exec function error: %w", err)) 62 63 if retryCount == r.opts.maxRetryCount { 64 return res, execErr 65 } 66 67 if err = fallbackFn(); err != nil && !r.opts.retryOnFallbackError { 68 execErr.AddErr(fmt.Errorf("fallback function error: %w", err)) 69 70 return res, execErr 71 } 72 73 retryCount++ 74 75 time.Sleep(r.opts.retryTimeout) 76 } 77 } 78 79 var ( 80 ErrMissingExecFn = errors.New("no exec function provided") 81 ErrMissingFallbackFn = errors.New("no fallback function provided") 82 ) 83 84 const ( 85 defaultMaxRetryCount = 3 86 defaultErrTimeout = 0 * time.Second 87 defaultRetryOnFallbackError = true 88 ) 89 90 // Opts holds the configurable options for a RetryableExecutor. 91 type Opts struct { 92 // maxRetryCount holds maximum number of retries in the case of failure. 93 maxRetryCount uint 94 95 // retryTimeout holds the timeout between retries. 96 retryTimeout time.Duration 97 98 // retryOnFallbackError specifies whether a retry will be done in the case of 99 // failure of the fallback function. 100 retryOnFallbackError bool 101 } 102 103 // NewDefaultExecOpts creates the default Opts. 104 func NewDefaultExecOpts() *Opts { 105 return &Opts{ 106 maxRetryCount: defaultMaxRetryCount, 107 retryTimeout: defaultErrTimeout, 108 retryOnFallbackError: defaultRetryOnFallbackError, 109 } 110 } 111 112 // OptsFn is function that operate on Opts. 113 type OptsFn func(opts *Opts) 114 115 // WithMaxRetryCount sets the max retry count. 116 // 117 // Note that a default value is provided if the provided count is 0. 118 func WithMaxRetryCount(maxRetryCount uint) OptsFn { 119 return func(opts *Opts) { 120 if maxRetryCount == 0 { 121 maxRetryCount = defaultMaxRetryCount 122 } 123 124 opts.maxRetryCount = maxRetryCount 125 } 126 } 127 128 // WithRetryTimeout sets the retry timeout. 129 func WithRetryTimeout(retryTimeout time.Duration) OptsFn { 130 return func(opts *Opts) { 131 opts.retryTimeout = retryTimeout 132 } 133 } 134 135 // WithRetryOnFallBackError sets the retryOnFallbackError flag. 136 func WithRetryOnFallBackError(retryOnFallbackError bool) OptsFn { 137 return func(opts *Opts) { 138 opts.retryOnFallbackError = retryOnFallbackError 139 } 140 } 141 142 // Error holds none or multiple errors that can happen during execution. 143 type Error struct { 144 errs []error 145 } 146 147 // AddErr appends an error to the error slice of Error. 148 func (e *Error) AddErr(err error) { 149 e.errs = append(e.errs, err) 150 } 151 152 // Error implements the standard error interface. 153 func (e *Error) Error() string { 154 sb := strings.Builder{} 155 156 for i, err := range e.errs { 157 sb.WriteString(fmt.Sprintf("error %d: %s\n", i, err)) 158 } 159 160 return sb.String() 161 }