Normandy.Resilience.Retry (normandy v2.2.0)

View Source

Retry mechanism with exponential backoff for handling transient failures.

Provides configurable retry logic for operations that may fail temporarily, such as network requests, API calls, or resource access.

Features

  • Exponential backoff with jitter
  • Configurable retry limits
  • Custom retry conditions
  • Detailed error tracking

Example

# Basic retry with defaults
Retry.with_retry(fn ->
  MyAPI.call()
end)

# Custom retry configuration
Retry.with_retry(
  fn -> MyAPI.call() end,
  max_attempts: 5,
  base_delay: 1000,
  max_delay: 30_000,
  retry_on: [:network_error, :rate_limit]
)

# With custom retry condition
Retry.with_retry(
  fn -> MyAPI.call() end,
  retry_if: fn error ->
    match?({:error, :temporary}, error)
  end
)

Summary

Functions

Create a retry configuration for common scenarios.

Checks retry options without running anything, raising ArgumentError on an unknown option or a bad value. BaseAgent.init/1 calls it for :retry, so a mistake surfaces when the agent is built.

Execute a function with retry logic.

Types

retry_option()

@type retry_option() ::
  {:max_attempts, pos_integer()}
  | {:base_delay, pos_integer()}
  | {:max_delay, pos_integer()}
  | {:backoff_factor, float()}
  | {:jitter, boolean()}
  | {:retry_on, [atom()]}
  | {:retry_if, (term() -> boolean())}

retry_options()

@type retry_options() :: [retry_option()] | %{optional(atom()) => term()}

Functions

preset(other)

@spec preset(atom()) :: [retry_option()]

Create a retry configuration for common scenarios.

Presets

  • :quick - Fast retries for quick operations (2 attempts, 100ms base)
  • :standard - Balanced configuration (3 attempts, 1s base)
  • :persistent - More aggressive retries (5 attempts, 1s base)
  • :patient - Long-running retries (10 attempts, 2s base)

Examples

Retry.with_retry(fn -> api_call() end, Retry.preset(:persistent))

validate_options!(opts)

@spec validate_options!(retry_options()) :: :ok

Checks retry options without running anything, raising ArgumentError on an unknown option or a bad value. BaseAgent.init/1 calls it for :retry, so a mistake surfaces when the agent is built.

with_retry(fun, opts \\ [])

@spec with_retry(function(), retry_options()) ::
  {:ok, term()} | {:error, {term(), pos_integer(), [term()]}}

Execute a function with retry logic.

Options

  • :max_attempts - Maximum number of attempts (default: 3)
  • :base_delay - Initial delay in milliseconds (default: 1000ms)
  • :max_delay - Maximum delay in milliseconds (default: 32000ms)
  • :backoff_factor - Exponential backoff multiplier (default: 2.0)
  • :jitter - Add randomness to delays (default: true)
  • :retry_on - List of error types to retry on (default: network errors, timeouts, rate limits)
  • :retry_if - Custom retry condition function

Options are a keyword list or a map. An unknown or repeated option or a bad value raises ArgumentError.

Returns

  • {:ok, result} - Success after 1 or more attempts
  • {:error, {reason, attempts, errors}} - every failure: reason is the last error, attempts how many were made (1 for a non-retryable error), and errors all of them in order. A return that is neither {:ok, _} nor {:error, _} is the error {:unexpected_return, value}.

Examples

# Retry with defaults
{:ok, result} = Retry.with_retry(fn ->
  {:ok, perform_operation()}
end)

# Custom configuration
{:ok, result} = Retry.with_retry(
  fn -> risky_operation() end,
  max_attempts: 5,
  base_delay: 500
)

# Custom retry condition
Retry.with_retry(
  fn -> custom_call() end,
  retry_if: fn
    {:error, %{status: status}} when status >= 500 -> true
    _ -> false
  end
)