Documentation ¶
Overview ¶
Package retry implements a retry mechanism based on configurable backoff strategies.
The fundamental structure is called Cycler. A cycler can be obtained by passing an appropriate backoff.Strategy to NewCycler. Any function whose signature matches AttemptFunc can then be retried using either Cycler.Try or Cycler.TryWithContext.
Index ¶
- func ForceExit(err error) error
- type AttemptFunc
- type Cycler
- func (c *Cycler) Cap(max time.Duration)
- func (c *Cycler) Jitter(spread float64)
- func (c *Cycler) Limit(n int)
- func (c *Cycler) OnError(handler ErrorHandlerFunc)
- func (c *Cycler) Timeout(limit time.Duration)
- func (c *Cycler) Try(attempt AttemptFunc) error
- func (c *Cycler) TryWithContext(ctx context.Context, attempt AttemptFunc) error
- type ErrorHandlerFunc
- type ExitError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type AttemptFunc ¶ added in v1.1.0
An AttemptFunc can be scheduled in a retry cycle. The function will be retried if it returns an error, while returning nil indicates successful completion. The argument n is the current attempt count, starting at n = 1.
type Cycler ¶
type Cycler struct { Clock backoff.Clock // used to track the execution time of retry cycles // contains filtered or unexported fields }
A Cycler is used to schedule retry cycles in which an AttemptFunc is repeatedly executed until it succeeds. Once configured, the same cycler can be used to schedule any number of retry cycles.
Example ¶
This example uses exponential backoff to retry a dummy function.
package main import ( "errors" "fmt" "time" "github.com/deep-rent/retry" "github.com/deep-rent/retry/backoff" ) func main() { exp := backoff.Exponential(2*time.Millisecond, 2.0) cycler := retry.NewCycler(exp) cycler.Cap(2 * time.Second) // cap backoff delay at 2 seconds cycler.Timeout(15 * time.Second) // stop retrying after 15 seconds // cycler.Jitter(0.5) // introduce 50% jitter // register an error handler cycler.OnError(func(n int, delay time.Duration, err error) { ms := delay.Milliseconds() fmt.Printf("attempt #%d: %v => wait %2d ms\n", n, err, ms) }) const N = 5 // number of tries // start retry cycle err := cycler.Try(func(n int) error { if n == N { // succeed after 5 attempts return nil } else { // force retry return errors.New("failed") } }) if err != nil { fmt.Printf("failed after retries: %v", err) } else { fmt.Printf("attempt #%d: succeeded", N) } }
Output: attempt #1: failed => wait 2 ms attempt #2: failed => wait 4 ms attempt #3: failed => wait 8 ms attempt #4: failed => wait 16 ms attempt #5: succeeded
func NewCycler ¶
NewCycler creates a new retry Cycler. The specified backoff.Strategy determines the backoff delay between consecutive attempts. A cycler is meant to be reused; recreating the same cycler should be avoided.
func (*Cycler) Cap ¶
Cap sets the maximum delay between consecutive attempts. If max <= 0, no limit will be applied.
func (*Cycler) Jitter ¶
Jitter randomly spreads delays between consecutive attempts around in time. The spread factor determines the relative range in which delays are scattered. It must fall in the half-open interval [0,1). For example, a spread of 0.5 results in delays ranging between 50% above and 50% below the values produced by the underlying backoff strategy. If spread = 0, no jitter will be applied.
func (*Cycler) Limit ¶
Limit sets the maximum number of attempts in a retry cycle. A retry cycle will stop after the n-th attempt. If n < 1, no limit will be applied.
func (*Cycler) OnError ¶
func (c *Cycler) OnError(handler ErrorHandlerFunc)
OnError registers a callback to be invoked when a failed AttemptFunc needs to be retried. Typically, these callbacks are used to log intermediate errors that would otherwise remain unhandled.
func (*Cycler) Timeout ¶
Timeout sets the maximum duration of retry cycles. A retry cycle will stop after the time elapsed since it was scheduled goes past the maximum. If limit <= 0, no timeout will be applied.
func (*Cycler) Try ¶
func (c *Cycler) Try(attempt AttemptFunc) error
Try calls [TryWithContext] using context.Background.
Example ¶
This example uses linear backoff to retry a dummy function.
package main import ( "errors" "fmt" "time" "github.com/deep-rent/retry" "github.com/deep-rent/retry/backoff" ) func main() { lin := backoff.Linear(5*time.Millisecond, 5*time.Millisecond) cycler := retry.NewCycler(lin) cycler.Limit(10) // stop retrying after 10 attempts // register an error handler cycler.OnError(func(n int, delay time.Duration, err error) { ms := delay.Milliseconds() fmt.Printf("attempt #%d: %v => wait %2d ms\n", n, err, ms) }) const N = 5 // number of tries // start retry cycle err := cycler.Try(func(n int) error { if n == N { // succeed after 5 attempts return nil } else { // force retry return errors.New("failed") } }) if err != nil { fmt.Printf("failed after retries: %v", err) } else { fmt.Printf("attempt #%d: succeeded", N) } }
Output: attempt #1: failed => wait 5 ms attempt #2: failed => wait 10 ms attempt #3: failed => wait 15 ms attempt #4: failed => wait 20 ms attempt #5: succeeded
func (*Cycler) TryWithContext ¶
func (c *Cycler) TryWithContext( ctx context.Context, attempt AttemptFunc, ) error
TryWithContext schedules a retry cycle in which attempt is repeatedly executed until it returns nil. The cycle stops early if
- some limit is exceeded,
- ctx is cancelled, or
- an ExitError occurs.
When an invocation of attempt returns nil before the cycle stops, this method also returns nil. Otherwise, this method returns the last error returned by attempt. If ctx contains an error, this error will be returned instead.
In any case, attempt is guaranteed to be executed at least once. Be aware that retry cycles with neither Cycler.Limit nor Cycler.Timeout set will run forever if attempt keeps failing.
Example ¶
This example uses a cancellable context to stop a retry cycle.
package main import ( "context" "errors" "fmt" "time" "github.com/deep-rent/retry" "github.com/deep-rent/retry/backoff" ) func main() { con := backoff.Constant(10 * time.Millisecond) cycler := retry.NewCycler(con) ctx, cancel := context.WithCancel(context.Background()) // register an error handler cycler.OnError(func(n int, delay time.Duration, err error) { ms := delay.Milliseconds() fmt.Printf("attempt #%d: %v => wait %2d ms\n", n, err, ms) }) const N = 5 // number of tries // start retry cycle err := cycler.TryWithContext(ctx, func(n int) error { if n == N { cancel() // succeed after 5 attempts return nil } // force retry return errors.New("failed") }) if err != nil && !errors.Is(err, context.Canceled) { fmt.Printf("failed after retries: %v", err) } else { fmt.Printf("attempt #%d: succeeded", N) } }
Output: attempt #1: failed => wait 10 ms attempt #2: failed => wait 10 ms attempt #3: failed => wait 10 ms attempt #4: failed => wait 10 ms attempt #5: succeeded
type ErrorHandlerFunc ¶ added in v1.1.0
An ErrorHandlerFunc is invoked when the n-th execution of an AttemptFunc failed with err, and the next retry is pending after delay has passed. Note that the initial execution corresponds to n = 1.
type ExitError ¶ added in v1.1.0
type ExitError struct {
Cause error
}
An ExitError signals that an AttemptFunc should no longer be retried. Use ForceExit to wrap an error such that it forces the current retry cycle to exit. This is useful when an error is encountered that the program cannot possibly recover after additional retries.