Current section
Files
Jump to
Current section
Files
examples/retry_example.exs
# Codex.Retry Usage Examples
#
# Run with: mix run examples/retry_example.exs
#
# This script demonstrates the various retry strategies and configurations
# available in Codex.Retry for handling transient failures.
alias Codex.Retry
IO.puts("""
=== Codex.Retry Examples ===
This example demonstrates retry logic with different backoff strategies.
""")
# -----------------------------------------------------------------------------
# Example 1: Basic retry with defaults
# -----------------------------------------------------------------------------
IO.puts("\n1. Basic retry with defaults")
IO.puts(" (4 max attempts, 200ms base delay, exponential backoff)")
counter = :counters.new(1, [])
result =
Retry.with_retry(
fn ->
attempt = :counters.get(counter, 1) + 1
:counters.add(counter, 1, 1)
IO.puts(" Attempt #{attempt}...")
if attempt < 3 do
{:error, :timeout}
else
{:ok, "success!"}
end
end,
# Using shorter delays for demo
base_delay_ms: 50
)
case result do
{:ok, value} -> IO.puts(" Result: #{value}")
{:error, reason} -> IO.puts(" Failed: #{inspect(reason)}")
end
# -----------------------------------------------------------------------------
# Example 2: Linear backoff strategy
# -----------------------------------------------------------------------------
IO.puts("\n2. Linear backoff strategy")
IO.puts(" (delays: 100ms, 200ms, 300ms, ...)")
counter = :counters.new(1, [])
start_time = System.monotonic_time(:millisecond)
Retry.with_retry(
fn ->
attempt = :counters.get(counter, 1) + 1
:counters.add(counter, 1, 1)
elapsed = System.monotonic_time(:millisecond) - start_time
IO.puts(" Attempt #{attempt} at #{elapsed}ms")
if attempt < 4 do
{:error, :timeout}
else
{:ok, :done}
end
end,
max_attempts: 5,
base_delay_ms: 100,
strategy: :linear,
jitter: false
)
# -----------------------------------------------------------------------------
# Example 3: Constant delay strategy
# -----------------------------------------------------------------------------
IO.puts("\n3. Constant delay strategy")
IO.puts(" (fixed 50ms delay between attempts)")
counter = :counters.new(1, [])
start_time = System.monotonic_time(:millisecond)
Retry.with_retry(
fn ->
attempt = :counters.get(counter, 1) + 1
:counters.add(counter, 1, 1)
elapsed = System.monotonic_time(:millisecond) - start_time
IO.puts(" Attempt #{attempt} at #{elapsed}ms")
if attempt < 3 do
{:error, :econnrefused}
else
{:ok, :connected}
end
end,
max_attempts: 4,
base_delay_ms: 50,
strategy: :constant,
jitter: false
)
# -----------------------------------------------------------------------------
# Example 4: Custom backoff function
# -----------------------------------------------------------------------------
IO.puts("\n4. Custom backoff function (Fibonacci-like)")
# Custom strategy: Fibonacci-like delays
fibonacci_delay = fn attempt ->
fib = fn fib, n ->
case n do
1 -> 50
2 -> 50
n -> fib.(fib, n - 1) + fib.(fib, n - 2)
end
end
fib.(fib, attempt)
end
counter = :counters.new(1, [])
start_time = System.monotonic_time(:millisecond)
Retry.with_retry(
fn ->
attempt = :counters.get(counter, 1) + 1
:counters.add(counter, 1, 1)
elapsed = System.monotonic_time(:millisecond) - start_time
IO.puts(" Attempt #{attempt} at #{elapsed}ms")
if attempt < 5 do
{:error, :timeout}
else
{:ok, :done}
end
end,
max_attempts: 6,
strategy: fibonacci_delay,
jitter: false
)
# -----------------------------------------------------------------------------
# Example 5: on_retry callback for logging
# -----------------------------------------------------------------------------
IO.puts("\n5. Using on_retry callback for logging")
counter = :counters.new(1, [])
Retry.with_retry(
fn ->
attempt = :counters.get(counter, 1) + 1
:counters.add(counter, 1, 1)
if attempt < 3 do
{:error, {:http_error, 503}}
else
{:ok, :service_available}
end
end,
max_attempts: 4,
base_delay_ms: 30,
jitter: false,
on_retry: fn attempt, error ->
IO.puts(" [WARN] Retry #{attempt + 1} after error: #{inspect(error)}")
end
)
# -----------------------------------------------------------------------------
# Example 6: Custom retry_if predicate
# -----------------------------------------------------------------------------
IO.puts("\n6. Custom retry_if predicate")
IO.puts(" (only retry on :retriable_error)")
counter = :counters.new(1, [])
# Custom predicate that only retries specific errors
custom_retry_if = fn
:retriable_error -> true
_ -> false
end
result =
Retry.with_retry(
fn ->
attempt = :counters.get(counter, 1) + 1
:counters.add(counter, 1, 1)
IO.puts(" Attempt #{attempt}...")
case attempt do
1 -> {:error, :retriable_error}
2 -> {:error, :retriable_error}
_ -> {:ok, "recovered"}
end
end,
max_attempts: 5,
base_delay_ms: 20,
jitter: false,
retry_if: custom_retry_if
)
IO.puts(" Result: #{inspect(result)}")
# Non-retriable error demonstration
IO.puts("\n Now with non-retriable error:")
counter = :counters.new(1, [])
result =
Retry.with_retry(
fn ->
attempt = :counters.get(counter, 1) + 1
:counters.add(counter, 1, 1)
IO.puts(" Attempt #{attempt}...")
{:error, :permanent_error}
end,
max_attempts: 5,
base_delay_ms: 20,
retry_if: custom_retry_if
)
IO.puts(" Result: #{inspect(result)} (no retries for permanent errors)")
# -----------------------------------------------------------------------------
# Example 7: Inspecting default retryable errors
# -----------------------------------------------------------------------------
IO.puts("\n7. Default retryable error types")
errors = [
:timeout,
:econnrefused,
:econnreset,
:closed,
:nxdomain,
{:http_error, 500},
{:http_error, 502},
{:http_error, 503},
{:http_error, 429},
{:http_error, 400},
{:http_error, 401},
:stream_reset,
:auth_failed,
:unknown_error
]
for error <- errors do
status = if Retry.retryable?(error), do: "✓ retryable", else: "✗ not retryable"
IO.puts(" #{inspect(error)} -> #{status}")
end
# -----------------------------------------------------------------------------
# Example 8: Calculate delay for different strategies
# -----------------------------------------------------------------------------
IO.puts("\n8. Delay calculations for different strategies")
strategies = [:exponential, :linear, :constant]
for strategy <- strategies do
IO.puts("\n #{strategy}:")
opts = [base_delay_ms: 100, max_delay_ms: 10_000, strategy: strategy, jitter: false]
for attempt <- 1..5 do
delay = Retry.calculate_delay(attempt, opts)
IO.puts(" Attempt #{attempt}: #{delay}ms")
end
end
# -----------------------------------------------------------------------------
# Example 9: Stream retry
# -----------------------------------------------------------------------------
IO.puts("\n9. Stream retry example")
counter = :counters.new(1, [])
stream =
Retry.with_stream_retry(
fn ->
attempt = :counters.get(counter, 1) + 1
:counters.add(counter, 1, 1)
IO.puts(" Creating stream (attempt #{attempt})...")
if attempt < 2 do
raise "Stream creation failed!"
else
Stream.map(1..3, fn x ->
IO.puts(" Yielding item: #{x}")
x * 10
end)
end
end,
max_attempts: 3,
base_delay_ms: 50,
jitter: false,
retry_if: fn _ -> true end
)
IO.puts(" Consuming stream...")
result = Enum.to_list(stream)
IO.puts(" Stream result: #{inspect(result)}")
# -----------------------------------------------------------------------------
# Example 10: Max delay cap demonstration
# -----------------------------------------------------------------------------
IO.puts("\n10. Max delay cap demonstration")
IO.puts(" (exponential with 500ms cap)")
opts = [base_delay_ms: 100, max_delay_ms: 500, strategy: :exponential, jitter: false]
for attempt <- 1..10 do
delay = Retry.calculate_delay(attempt, opts)
uncapped = round(100 * :math.pow(2, attempt - 1))
IO.puts(" Attempt #{attempt}: #{delay}ms (uncapped would be #{uncapped}ms)")
end
IO.puts("""
=== Examples Complete ===
Key takeaways:
- Use exponential backoff for most network/API calls (default)
- Use linear backoff when you want predictable delay growth
- Use constant backoff for simple fixed-interval retries
- Use custom strategies for specialized requirements
- Always set appropriate max_delay_ms caps
- Use jitter (enabled by default) to prevent thundering herd
- Use on_retry callbacks for observability/logging
""")