Documentation
¶
Overview ¶
Package retrier provides a configurable retry engine for executing a task function with backoff, jitter, and per-attempt timeouts.
Problem ¶
Transient failures are common in distributed systems (temporary network issues, short-lived upstream overload, brief lock/contention windows). Retrying can dramatically improve success rates, but ad hoc retry loops often miss important details such as cancellation propagation, bounded attempts, timeout isolation per attempt, and jitter to avoid synchronized retry storms.
This package centralizes those concerns in a reusable retrier implementation.
How It Works ¶
New creates a Retrier with defaults, or with custom Option values. Retrier.Run then executes a TaskFn according to configured retry rules:
- Execute the task with a per-attempt timeout context.
- Evaluate the result with a retry predicate (RetryIfFn).
- Stop when attempts are exhausted or retry is not required.
- Otherwise schedule the next attempt after delay + random jitter.
- Increase delay by the configured multiplication factor for successive retries.
The run loop always respects parent context cancellation.
Defaults ¶
- attempts: DefaultAttempts (4)
- initial delay: DefaultDelay (1s)
- delay factor: DefaultDelayFactor (2)
- jitter: DefaultJitter (1ms)
- per-attempt timeout: DefaultTimeout (1s)
- maximum delay: unbounded (use WithMaxDelay to cap the pre-jitter backoff)
- retry condition: DefaultRetryIf (retry on any non-nil error)
Key Features ¶
- Pluggable retry condition via WithRetryIfFn.
- Bounded total attempts via WithAttempts.
- Configurable delay, exponential factor, jitter, maximum delay, and jitter strategy via WithDelay, WithDelayFactor, WithJitter, WithMaxDelay, and WithJitterStrategy.
- Per-attempt timeout isolation via WithTimeout.
- Optional retry observability via WithOnRetry.
- Context-aware cancellation for clean shutdown behavior.
Usage ¶
r, err := retrier.New(
retrier.WithAttempts(5),
retrier.WithDelay(200*time.Millisecond),
retrier.WithDelayFactor(2),
retrier.WithJitter(25*time.Millisecond),
)
if err != nil {
return err
}
err = r.Run(ctx, func(ctx context.Context) error {
return callExternalService(ctx)
})
if err != nil {
return err
}
This package is ideal for retrying idempotent or safe-to-repeat operations in networked and high-concurrency Go services.
Index ¶
- Constants
- func DefaultRetryIf(err error) bool
- type JitterStrategy
- type OnRetryFn
- type Option
- func WithAttempts(attempts uint) Option
- func WithDelay(delay time.Duration) Option
- func WithDelayFactor(delayFactor float64) Option
- func WithJitter(jitter time.Duration) Option
- func WithJitterStrategy(strategy JitterStrategy) Option
- func WithMaxDelay(maxDelay time.Duration) Option
- func WithOnRetry(onRetry OnRetryFn) Option
- func WithRetryIfFn(retryIfFn RetryIfFn) Option
- func WithTimeout(timeout time.Duration) Option
- type Retrier
- type RetryIfFn
- type TaskFn
Examples ¶
Constants ¶
const ( // DefaultAttempts is the default maximum number of total attempts (the // initial call plus retries). DefaultAttempts = 4 // DefaultDelay is the default delay to apply after the first failed attempt. DefaultDelay = 1 * time.Second // DefaultDelayFactor is the default multiplication factor to get the successive delay value. DefaultDelayFactor = 2 // DefaultJitter is the default maximum random Jitter time between retries. DefaultJitter = 1 * time.Millisecond // DefaultTimeout is the default timeout applied to each function call via context. DefaultTimeout = 1 * time.Second )
const ( JitterAdditive = backoff.JitterAdditive // fixed additive ceiling (default) JitterFull = backoff.JitterFull // rand[0, delay): best decorrelation JitterEqual = backoff.JitterEqual // delay/2 + rand[0, delay/2) )
Jitter strategies, re-exported from backoff so callers need not import it.
Variables ¶
This section is empty.
Functions ¶
func DefaultRetryIf ¶
DefaultRetryIf is the default retry predicate: returns true if error is non-nil.
Types ¶
type JitterStrategy ¶
type JitterStrategy = backoff.JitterStrategy
JitterStrategy selects how backoff jitter is applied. See backoff.JitterStrategy.
type OnRetryFn ¶
OnRetryFn is an optional observability callback invoked before each scheduled retry. It receives the number of the attempt that just failed (1-based), the delay before the next attempt, and the error that triggered the retry. It counts scheduled retries — one may still be preempted by cancellation before it runs — and must not panic; it runs inline in Retrier.Run.
type Option ¶
Option is the interface that allows to set the options.
func WithAttempts ¶
WithAttempts customizes the maximum number of retry attempts. Returns error if attempts < 1.
func WithDelay ¶
WithDelay customizes the base delay after the first failed attempt. Returns error if delay < 1 nanosecond.
func WithDelayFactor ¶
WithDelayFactor customizes the exponential backoff multiplier (factor > 1 for exponential growth). Returns error if delayFactor < 1.
func WithJitter ¶
WithJitter customizes the maximum random jitter added to each retry delay to avoid thundering-herd. Returns error if jitter < 1 nanosecond.
func WithJitterStrategy ¶
func WithJitterStrategy(strategy JitterStrategy) Option
WithJitterStrategy selects how backoff jitter is applied (default JitterAdditive). JitterFull and JitterEqual scale jitter with the delay for better decorrelation. Returns error if the strategy is not a defined JitterStrategy.
func WithMaxDelay ¶
WithMaxDelay caps the pre-jitter exponential backoff delay (default: unbounded, subject only to the internal safety cap). Returns error if maxDelay < 1 nanosecond.
func WithOnRetry ¶
WithOnRetry registers an observability callback invoked before each scheduled retry (see OnRetryFn). Returns error if the callback is nil.
func WithRetryIfFn ¶
WithRetryIfFn customizes the retry condition predicate. Returns error if the function is nil.
func WithTimeout ¶
WithTimeout customizes the per-attempt timeout applied via context.WithTimeout(). Returns error if timeout < 1 nanosecond.
type Retrier ¶
type Retrier struct {
// contains filtered or unexported fields
}
Retrier applies configurable retry logic to generic task functions.
A configured Retrier is immutable: it holds no per-run state, so a single instance is safe to share and to call concurrently from multiple goroutines.
func (*Retrier) Run ¶
Run executes the task with exponential backoff and jitter, respecting parent context cancellation.
An already-canceled context fails fast without running the task. If ctx is canceled during or just before the first attempt, the task may still run once (with the canceled context); a well-behaved task returns promptly.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
"time"
"github.com/tecnickcom/nurago/pkg/retrier"
)
func main() {
var count int
// example function that returns nil only at the third attempt.
task := func(_ context.Context) error {
if count == 2 {
return nil
}
count++
return errors.New("ERROR")
}
opts := []retrier.Option{
retrier.WithRetryIfFn(retrier.DefaultRetryIf),
retrier.WithAttempts(5),
retrier.WithDelay(10 * time.Millisecond),
retrier.WithDelayFactor(1.1),
retrier.WithJitter(5 * time.Millisecond),
retrier.WithTimeout(2 * time.Millisecond),
}
r, err := retrier.New(opts...)
if err != nil {
log.Fatal(err)
}
timeout := 1 * time.Second
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
err = r.Run(ctx, task)
cancel()
if err != nil {
log.Fatal(err)
}
fmt.Println(count)
}
Output: 2
type RetryIfFn ¶
RetryIfFn is the signature of the function used to decide when retry. It must not panic; it runs inline in Retrier.Run.