Documentation
¶
Overview ¶
Package nxretry implements a context-aware retrier with an optional and customizable backoff.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Backoff ¶
type Backoff interface {
// TODO: this interface cannot be publicly implemented, making external
// Backoff implementations impossible.
Option
// Delay returns the [time.Duration] to wait before running the given attempt.
Delay(attempt uint) time.Duration
}
Backoff represents an abstract backoff implementation.
This is used to abstract the duration to wait between attempts, to allow for different algorithms/implementations.
type ContextFactory ¶
ContextFactory is a function that takes a context and returns a modified context, optionally with an associated cancel function. It is required to return a non-nil context.Context, but the context.ContextFunc may be `nil` or an empty function `func() { }`.
type Exponential ¶
type Exponential struct {
// Factor is the factor at which Min will increase after each failed attempt.
Factor float64
// Min is the initial backoff time to wait after the first failed attempt.
Min time.Duration
// Max is the maximum time to wait before retrying.
Max time.Duration
}
Exponential is the implementation of an exponential Backoff.
func (Exponential) Delay ¶
func (e Exponential) Delay(attempt uint) time.Duration
Delay returns the time.Duration to wait before running the given attempt.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option for a Retry.
type OptionFunc ¶
type OptionFunc func(o *options)
OptionFunc type is an adapter to allow the use of ordinary functions as an Option. If f is a function with the appropriate signature, `OptionFunc(f)` is an Option that calls f.
func MaxAttempts ¶
func MaxAttempts(maxAttempts uint) OptionFunc
MaxAttempts sets the maximum number of attempts that can occur. If set to 0 the maximum number of attempts will be unlimited.
func WithContextFactory ¶
func WithContextFactory(f ContextFactory) OptionFunc
WithContextFactory sets the ContextFactory used by a Retry.
The context factory is invoked before each attempt and the returned context.Context is passed through by the [Retry.Next] iterator. If a non-nil context.CancelFunc is provided by the context factory, it will be called after the current iteration completes.
type Retry ¶
type Retry interface {
// Reset resets the [Retry] instance back to its original state.
//
// [Retry.Attempt], [Retry.Delay] and [Retry.Override], will be all be reset
// to `0`.
Reset()
// Attempt returns the number of the current attempt.
Attempt() uint
// Delay returns the [time.Duration] to wait for the next attempt.
//
// The delay will be calculated using the [Backoff] configured on the [Retry]
// instance. If [Retry.Override] has been used, its value will be returned
// instead, until it has been consumed by an iteration of [Retry.Next].
//
// This function is useful for logging when the next attempt will occur.
Delay() time.Duration
// Next increments the attempt, then waits for the duration of the attempt.
// Once the duration has passed, Next returns true. Next will return false if
// the attempt will exceed the MaxAttempts limit or if the given context has
// been canceled.
//
// This function was designed to be used as follows:
//
// r := New()
// for ctx := range r.Next(ctx) {
// // Do work, `continue` on soft-failure, `break` on success or non-retryable error.
// }
Next(ctx context.Context) iter.Seq[context.Context]
// Override overrides the delay for the [Retry.Next] iteration.
//
// If a value of `0` is given, no override will be performed on the next
// iteration.
//
// Calling [Retry.Override] multiple times before an iteration if safe, but
// only the value from the last call will be used.
//
// The value of [Retry.Attempt] will still be incremented and count towards
// the maximum attempt limit, however [Retry.Delay] will return a delay
// as-if the overridden attempt never occurred. This is to allow for
// temporary overrides without exceeding the maximum attempt limit or
// changing the [Retry.Delay] for subsequent non-overridden attempts.
//
// [Retry.Delay] will temporarily return the value of `d`, until the
// next iteration where the override will be reset. After the override
// has been reset, [Retry.Delay] will return a delay as-if the overridden
// attempt never occurred. This is to allow for temporary overrides without
// exceeding the maximum attempt limit or affecting the delay for subsequent
// non-overridden attempts.
//
// Subsequent iterations of [Retry.Next] will continue to use the [Backoff]
// configured on the [Retry] instance, unless [Retry.Override] is called
// again before the next iteration.
Override(d time.Duration)
}
Retry represents a retrier implementation.
func New ¶
New creates a new retrier.
Example ¶
package main
import (
"context"
"time"
"github.com/matthewpi/nxretry"
)
func main() {
r := nxretry.New(
nxretry.MaxAttempts(3),
nxretry.Exponential{
Factor: 2,
Min: 1 * time.Second,
Max: 5 * time.Second,
},
)
// Run code with the ability to retry, optionally using the provided context.
for ctx := range r.Next(context.Background()) {
_ = ctx
// Do something.
//
// `break` on success (or if you don't want to retry anymore) and `continue` on failure.
}
}
Output:
Example (ContextFactory) ¶
package main
import (
"context"
"time"
"github.com/matthewpi/nxretry"
)
func main() {
r := nxretry.New(
nxretry.MaxAttempts(3),
nxretry.Exponential{
Factor: 2,
Min: 1 * time.Second,
Max: 5 * time.Second,
},
// For each attempt create a context with a dedicated timeout to prevent
// any individual attempt from being attempted for too long.
nxretry.WithContextFactory(func(ctx context.Context) (context.Context, context.CancelFunc) {
return context.WithTimeout(ctx, 5*time.Second)
}),
)
// Run code with the ability to retry, optionally using the provided context.
for ctx := range r.Next(context.Background()) {
_ = ctx // Context with timeout from the context factory.
// Do something.
//
// `break` on success (or if you don't want to retry anymore) and `continue` on failure.
}
}
Output:
Example (NoContext) ¶
package main
import (
"context"
"time"
"github.com/matthewpi/nxretry"
)
func main() {
r := nxretry.New(
nxretry.MaxAttempts(3),
nxretry.Exponential{
Factor: 2,
Min: 1 * time.Second,
Max: 5 * time.Second,
},
)
// Run code with the ability to retry.
for range r.Next(context.Background()) {
// Do something.
//
// `break` on success (or if you don't want to retry anymore) and `continue` on failure.
}
}
Output:
type Timer ¶
type Timer interface {
// C returns the channel where the current [time.Time] will be sent when the
// timer fires. Calling [Timer.C] before [Timer.Start] will return nil, if
// you ever find yourself nil-checking the result of this function, you are
// likely using this interface incorrectly.
C() <-chan time.Time
// Start starts a timer using the specified duration. If Start is being
// called for the first time it will create a new underlying timer,
// otherwise it will reset the existing timer to a new duration.
//
// If you are re-using the same timer by calling Start multiple times,
// ensure that the channel returned by [Timer.C] was drained or that
// [Timer.Stop] was called properly according to its documentation.
//
// Essentially always ensure that the channel was drained if a value was
// ever sent.
Start(time.Duration)
// Stop prevents the Timer from firing.
//
// It returns true if the call stops the timer, false if the timer has
// already expired or been stopped.
//
// Stop does not close the channel, to prevent a read from the channel
// succeeding incorrectly.
//
// To ensure the channel is empty after a call to Stop, check the
// return value and drain the channel.
// For example, assuming the program has not received from t.C already:
//
// if !t.Stop() {
// <-t.C()
// }
//
// This cannot be done concurrent to other receives from the Timer's
// channel or other calls to the Timer's Stop method.
Stop() bool
}
Timer is used as an abstraction to swap out the timer implementation used by Backoff. Most users will not need to implement this interface, it is used for mocking during tests.