HTTPower.Retry (HTTPower v0.16.0)
View SourceRetry logic with exponential backoff and jitter for HTTP requests.
Provides resilient HTTP execution by automatically retrying failed requests with intelligent backoff strategies. Retry is an execution wrapper that sits between the middleware pipeline and the HTTP adapter layer.
How It Works
- Request Execution - Calls the HTTP adapter
- Response Analysis - Checks if the response/error is retryable
- Retry Decision - Decides whether to retry based on:
- HTTP status codes (408, 429, 500, 502, 503, 504)
- Transport errors (timeout, closed, econnrefused, econnreset if safe)
- Remaining retry attempts
- Backoff Calculation - Calculates delay using exponential backoff with jitter
- Retry Execution - Waits and retries the request
Configuration
# Global defaults (can be overridden per-request)
HTTPower.get(url,
max_retries: 3, # Maximum retry attempts (default: 3)
retry_safe: false, # Retry on connection reset (default: false)
base_delay: 1000, # Base delay in ms (default: 1000)
max_delay: 30_000, # Maximum delay cap in ms (default: 30000)
jitter_factor: 0.2 # Jitter randomization 0.0-1.0 (default: 0.2)
)Retry-After Header Support
For 429 (Too Many Requests) and 503 (Service Unavailable) responses,
HTTPower automatically respects the Retry-After header if present:
# Server sends: Retry-After: 5
# HTTPower waits exactly 5 seconds instead of exponential backoff
{:ok, response} = HTTPower.get(url)Retryable Errors
HTTP Status Codes:
- 408 Request Timeout
- 429 Too Many Requests
- 500 Internal Server Error
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
Transport Errors:
:timeout- Request timeout:closed- Connection closed:econnrefused- Connection refused:econnreset- Connection reset (only ifretry_safe: true)
Exponential Backoff Formula
delay = min(max_delay, base_delay * 2^(attempt-1)) * (1 - jitter * random())Example delays (base_delay: 1000, jitter_factor: 0.2):
- Attempt 1: 800-1000ms
- Attempt 2: 1600-2000ms
- Attempt 3: 3200-4000ms
Examples
# Retry with custom configuration
HTTPower.get("https://flaky-api.com",
max_retries: 5,
base_delay: 2000,
max_delay: 60_000
)
# Enable retry on connection reset
HTTPower.get("https://api.example.com",
retry_safe: true
)
# Check if error is retryable
HTTPower.Retry.retryable_status?(500) # true
HTTPower.Retry.retryable_status?(404) # falseArchitecture Note
Retry is NOT implemented as middleware because:
- Middleware run BEFORE HTTP execution (request processing)
- Retry runs DURING HTTP execution (execution wrapper)
- Middleware run once, retry may execute multiple times
- This separation ensures middleware coordination works correctly:
- Circuit breaker evaluates once per logical request (not per retry attempt)
- Rate limiter consumes token once (retries don't consume extra tokens)
- Dedup treats retries as same logical request
Summary
Functions
Calculates exponential backoff delay with jitter.
Executes an HTTP request with retry logic.
Checks if an error reason is retryable.
Checks if an HTTP status code is retryable.
Functions
Calculates exponential backoff delay with jitter.
The delay increases exponentially with each attempt, capped at max_delay,
and randomized with jitter to prevent thundering herd.
Parameters
attempt- Current attempt number (1-based)retry_opts- Map with :base_delay, :max_delay, :jitter_factor
Formula
delay = min(max_delay, base_delay * 2^(attempt-1)) * (1 - jitter_factor * random())Examples
iex> opts = %{base_delay: 1000, max_delay: 30_000, jitter_factor: 0.2}
iex> HTTPower.Retry.calculate_backoff_delay(1, opts)
800..1000 # Range due to jitter
iex> HTTPower.Retry.calculate_backoff_delay(2, opts)
1600..2000
iex> HTTPower.Retry.calculate_backoff_delay(3, opts)
3200..4000
Executes an HTTP request with retry logic.
This is the main entry point called by HTTPower.Client. It wraps the
HTTP adapter call with retry logic and exponential backoff.
Parameters
method- HTTP method (:get, :post, :put, :delete)url- Request URL (URI struct or string)body- Request body (string or nil)headers- Request headers (map)adapter- HTTP adapter module or {module, config} tupleopts- Request options (includes retry configuration)
Returns
{:ok, HTTPower.Response.t()}on success{:error, HTTPower.Error.t()}on failure (after exhausting retries)
Examples
HTTPower.Retry.execute_with_retry(
:get,
URI.parse("https://api.example.com"),
nil,
%{},
HTTPower.Adapter.Finch,
[max_retries: 3]
)
Checks if an error reason is retryable.
Takes into account the retry_safe configuration for connection reset errors.
Parameters
reason- Error reason (HTTP status tuple, transport error, or atom)retry_safe- Whether to retry on connection reset (boolean)
Examples
iex> HTTPower.Retry.retryable_error?({:http_status, 500, response}, false)
true
iex> HTTPower.Retry.retryable_error?(:timeout, false)
true
iex> HTTPower.Retry.retryable_error?(:econnreset, false)
false
iex> HTTPower.Retry.retryable_error?(:econnreset, true)
true
Checks if an HTTP status code is retryable.
Examples
iex> HTTPower.Retry.retryable_status?(500)
true
iex> HTTPower.Retry.retryable_status?(404)
false