Moov.Retry (Moov v1.0.0)

Copy Markdown View Source

A small, dependency-free retry-with-backoff helper.

Moov.Client uses this to automatically retry requests that fail with a transient error: 429 (rate limited), 5xx (Moov degraded), or a transport-level network error. Non-transient failures (400, 401, 404, 409, 422, decode errors, ...) are never retried, since retrying a request that was rejected for being invalid will just fail again.

Delay between attempts is computed as exponential backoff with full jitter, capped at :max_delay_ms - except when Moov's response body or a standard Retry-After header tells us exactly how long to wait (Moov.Error.retry_after_ms/0), in which case that takes precedence.

Summary

Functions

Computes the delay (in milliseconds) before the next attempt.

Whether the given Moov.Error represents a transient failure worth retrying.

Calls fun and, if it returns {:error, error} for a retryable error, calls it again (up to :max_retries additional times) with an exponentially increasing delay between attempts.

Types

result()

@type result() :: {:ok, term()} | {:error, Moov.Error.t()}

Functions

delay_ms(error, attempt, base_delay_ms, max_delay_ms)

Computes the delay (in milliseconds) before the next attempt.

Prefers an explicit retry_after_ms from the error (Moov told us exactly how long to wait) and otherwise falls back to exponential backoff with full jitter: a random value between 0 and min(base_delay_ms * 2^(attempt - 1), max_delay_ms).

retryable?(error)

@spec retryable?(Moov.Error.t()) :: boolean()

Whether the given Moov.Error represents a transient failure worth retrying.

run(fun, opts \\ [])

@spec run(
  (-> result()),
  keyword()
) :: result()

Calls fun and, if it returns {:error, error} for a retryable error, calls it again (up to :max_retries additional times) with an exponentially increasing delay between attempts.

Options

  • :max_retries - maximum number of additional attempts after the first one fails (default: 3)
  • :base_delay_ms - base delay before the first retry (default: 250)
  • :max_delay_ms - upper bound for the computed delay (default: 8_000)
  • :sleep_fun - function called with the computed delay in milliseconds instead of Process.sleep/1; override this in tests so retry tests don't actually take seconds to run
  • :on_retry - a 3-arity function called with (attempt_number, delay_ms, error) before each retry, useful for logging or telemetry

Examples

Moov.Retry.run(fn -> do_request() end, max_retries: 3)