123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- package backoff
- import (
- "errors"
- "time"
- )
- // An OperationWithData is executing by RetryWithData() or RetryNotifyWithData().
- // The operation will be retried using a backoff policy if it returns an error.
- type OperationWithData[T any] func() (T, error)
- // An Operation is executing by Retry() or RetryNotify().
- // The operation will be retried using a backoff policy if it returns an error.
- type Operation func() error
- func (o Operation) withEmptyData() OperationWithData[struct{}] {
- return func() (struct{}, error) {
- return struct{}{}, o()
- }
- }
- // Notify is a notify-on-error function. It receives an operation error and
- // backoff delay if the operation failed (with an error).
- //
- // NOTE that if the backoff policy stated to stop retrying,
- // the notify function isn't called.
- type Notify func(error, time.Duration)
- // Retry the operation o until it does not return error or BackOff stops.
- // o is guaranteed to be run at least once.
- //
- // If o returns a *PermanentError, the operation is not retried, and the
- // wrapped error is returned.
- //
- // Retry sleeps the goroutine for the duration returned by BackOff after a
- // failed operation returns.
- func Retry(o Operation, b BackOff) error {
- return RetryNotify(o, b, nil)
- }
- // RetryWithData is like Retry but returns data in the response too.
- func RetryWithData[T any](o OperationWithData[T], b BackOff) (T, error) {
- return RetryNotifyWithData(o, b, nil)
- }
- // RetryNotify calls notify function with the error and wait duration
- // for each failed attempt before sleep.
- func RetryNotify(operation Operation, b BackOff, notify Notify) error {
- return RetryNotifyWithTimer(operation, b, notify, nil)
- }
- // RetryNotifyWithData is like RetryNotify but returns data in the response too.
- func RetryNotifyWithData[T any](operation OperationWithData[T], b BackOff, notify Notify) (T, error) {
- return doRetryNotify(operation, b, notify, nil)
- }
- // RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
- // for each failed attempt before sleep.
- // A default timer that uses system timer is used when nil is passed.
- func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
- _, err := doRetryNotify(operation.withEmptyData(), b, notify, t)
- return err
- }
- // RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too.
- func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
- return doRetryNotify(operation, b, notify, t)
- }
- func doRetryNotify[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
- var (
- err error
- next time.Duration
- res T
- )
- if t == nil {
- t = &defaultTimer{}
- }
- defer func() {
- t.Stop()
- }()
- ctx := getContext(b)
- b.Reset()
- for {
- res, err = operation()
- if err == nil {
- return res, nil
- }
- var permanent *PermanentError
- if errors.As(err, &permanent) {
- return res, permanent.Err
- }
- if next = b.NextBackOff(); next == Stop {
- if cerr := ctx.Err(); cerr != nil {
- return res, cerr
- }
- return res, err
- }
- if notify != nil {
- notify(err, next)
- }
- t.Start(next)
- select {
- case <-ctx.Done():
- return res, ctx.Err()
- case <-t.C():
- }
- }
- }
- // PermanentError signals that the operation should not be retried.
- type PermanentError struct {
- Err error
- }
- func (e *PermanentError) Error() string {
- return e.Err.Error()
- }
- func (e *PermanentError) Unwrap() error {
- return e.Err
- }
- func (e *PermanentError) Is(target error) bool {
- _, ok := target.(*PermanentError)
- return ok
- }
- // Permanent wraps the given err in a *PermanentError.
- func Permanent(err error) error {
- if err == nil {
- return nil
- }
- return &PermanentError{
- Err: err,
- }
- }
|