retrier

retrier provides a configurable retry engine for executing a task function with backoff, jitter, and per-attempt timeouts.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/retrier"

Package retrier provides a configurable retry engine for executing a task function with backoff, jitter, and per-attempt timeouts.

The delay schedule and jitter strategies are those of backoff, so one retry policy can be applied consistently to HTTP and non-HTTP work.

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:

  1. Execute the task with a per-attempt timeout context.
  2. Evaluate the result with a retry predicate (RetryIfFn).
  3. Stop when attempts are exhausted or retry is not required.
  4. Otherwise schedule the next attempt after delay + random jitter.
  5. 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)

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
}

When To Use

  • A task talks to a flaky dependency and should be retried on a subset of errors.
  • Each attempt needs its own timeout, separate from the overall deadline.
  • You want retry decisions driven by a predicate on the returned error.

Example

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

Full source is in example_retrier_test.go. More runnable examples are on pkg.go.dev.

Dependencies

This package reaches no external module: it uses only the Go standard library.