ExMCP.Reliability.Retry (ex_mcp v0.10.0)

View Source

Retry logic with exponential backoff for MCP operations.

Provides configurable retry strategies to handle transient failures in distributed systems. Supports exponential backoff with jitter to prevent thundering herd problems.

Usage

# Simple retry with defaults
Retry.with_retry(fn ->
  ExMCP.Client.call_tool(client, "tool", %{})
end)

# Custom configuration
Retry.with_retry(
  fn -> risky_operation() end,
  max_attempts: 5,
  initial_delay: 100,
  max_delay: 10_000,
  backoff_factor: 2,
  jitter: true
)

# With custom retry condition
Retry.with_retry(
  fn -> http_request() end,
  should_retry?: fn
    {:error, %{status: status}} when status in 500..599 -> true
    {:error, :timeout} -> true
    _ -> false
  end
)

Strategies

  • Exponential backoff: Delay increases exponentially with each attempt
  • Jitter: Random variation to prevent synchronized retries
  • Circuit breaker integration: Can be combined with circuit breakers

Summary

Functions

Calculates the delay for a given attempt using exponential backoff.

Retry configuration specifically for MCP operations.

Merges client default retry policy with operation-specific overrides.

Executes multiple operations with retry, stopping on first success.

Executes a function with linear retry (fixed delay).

Executes a function with retry logic.

Creates a retry-wrapped version of a function.

Types

retry_opts()

@type retry_opts() :: [
  max_attempts: pos_integer(),
  initial_delay: pos_integer(),
  max_delay: pos_integer(),
  backoff_factor: number(),
  jitter: boolean(),
  should_retry?: (any() -> boolean()),
  on_retry: (pos_integer(), any() -> any())
]

Functions

calculate_delay(attempt, opts \\ [])

@spec calculate_delay(
  pos_integer(),
  keyword()
) :: pos_integer()

Calculates the delay for a given attempt using exponential backoff.

Useful for custom retry implementations.

mcp_defaults(overrides \\ [])

@spec mcp_defaults(keyword()) :: keyword()

Retry configuration specifically for MCP operations.

Returns retry options optimized for MCP protocol operations.

merge_policies(client_defaults, operation_overrides)

@spec merge_policies(keyword(), keyword()) :: keyword()

Merges client default retry policy with operation-specific overrides.

Operation-specific values take precedence over client defaults.

Examples

iex> ExMCP.Reliability.Retry.merge_policies(
...>   [max_attempts: 5, initial_delay: 100],
...>   [max_attempts: 3]
...> )
[max_attempts: 3, initial_delay: 100]

with_fallback(functions, opts \\ [])

@spec with_fallback([function()], retry_opts()) ::
  {:ok, any()} | {:error, :all_failed}

Executes multiple operations with retry, stopping on first success.

Useful for fallback scenarios where you have multiple ways to achieve the same result.

Example

Retry.with_fallback([
  fn -> primary_service_call() end,
  fn -> secondary_service_call() end,
  fn -> fallback_local_data() end
])

with_linear_retry(fun, opts \\ [])

@spec with_linear_retry(
  function(),
  keyword()
) :: {:ok, any()} | {:error, any()}

Executes a function with linear retry (fixed delay).

Options

  • :max_attempts - Maximum number of attempts (default: 3)
  • :delay - Fixed delay between attempts in ms (default: 1000)
  • :should_retry? - Function to determine if retry should occur
  • :on_retry - Callback function called before each retry

with_retry(fun, opts \\ [])

@spec with_retry(function(), retry_opts()) :: {:ok, any()} | {:error, any()}

Executes a function with retry logic.

Options

  • :max_attempts - Maximum number of attempts (default: 3)
  • :initial_delay - Initial delay in ms (default: 100)
  • :max_delay - Maximum delay in ms (default: 5000)
  • :backoff_factor - Multiplier for exponential backoff (default: 2)
  • :jitter - Add randomization to delays (default: true)
  • :should_retry? - Function to determine if retry should occur (default: retry on any error)
  • :on_retry - Callback function called before each retry with (attempt, error)

Returns

  • {:ok, result} if operation succeeds
  • {:error, reason} if all retries are exhausted

wrap(fun, opts \\ [])

@spec wrap(function(), retry_opts()) :: function()

Creates a retry-wrapped version of a function.

Useful for wrapping functions that should always be retried.

Example

retryable_call = Retry.wrap(fn -> unstable_api_call() end, max_attempts: 5)

# Later...
result = retryable_call.()