Module Retry

HTTP request retry logic with exponential backoff

This module provides configurable retry logic for HTTP requests, including exponential backoff, custom retry predicates, and Retry-After header support per RFC 9110 Section 10.2.3.

Custom Retry Predicates

Per Recommendation #14: You can define custom predicates to control retry behavior beyond the built-in status code and method checks.

Example: Retry on specific error responses

  let retry_on_rate_limit method_ status headers =
    status = 429 && Headers.get "x-retry-allowed" headers = Some "true"
  in
  let config = Retry.create_config
    ~retry_response:retry_on_rate_limit
    ()

Example: Retry on custom exceptions

  let retry_on_network_error = function
    | Unix.Unix_error (Unix.ECONNRESET, _, _) -> true
    | Unix.Unix_error (Unix.ETIMEDOUT, _, _) -> true
    | _ -> false
  in
  let config = Retry.create_config
    ~retry_exception:retry_on_network_error
    ()
val src : Logs.Src.t

Log source for retry operations

Custom Retry Predicates

Per Recommendation #14: Allow user-defined retry logic.

type response_predicate = Method.t -> int -> Headers.t -> bool

Custom retry predicate for responses. Receives (method, status, headers) and returns true to retry. This runs in addition to the built-in status_forcelist check.

type exception_predicate = exn -> bool

Custom retry predicate for exceptions. Returns true if the exception should trigger a retry.

Configuration

type config = {
  1. max_retries : int;
    (*

    Maximum number of retry attempts

    *)
  2. backoff_factor : float;
    (*

    Exponential backoff multiplier

    *)
  3. backoff_max : float;
    (*

    Maximum backoff time in seconds

    *)
  4. status_forcelist : int list;
    (*

    HTTP status codes to retry

    *)
  5. allowed_methods : Method.t list;
    (*

    Methods safe to retry

    *)
  6. respect_retry_after : bool;
    (*

    Honor Retry-After response header

    *)
  7. jitter : bool;
    (*

    Add randomness to prevent thundering herd

    *)
  8. retry_response : response_predicate option;
    (*

    Custom response retry predicate

    *)
  9. retry_exception : exception_predicate option;
    (*

    Custom exception retry predicate

    *)
  10. strict_method_semantics : bool;
    (*

    When true, raise an error if asked to retry a non-idempotent method. Per RFC 9110 Section 9.2.2: Non-idempotent methods should not be retried automatically as the request may have already been processed. Default is false (just log and skip retry).

    *)
}

Retry configuration

val default_config : config

Default retry configuration

val create_config : ?max_retries:int -> ?backoff_factor:float -> ?backoff_max:float -> ?status_forcelist:int list -> ?allowed_methods:Method.t list -> ?respect_retry_after:bool -> ?jitter:bool -> ?retry_response:response_predicate -> ?retry_exception:exception_predicate -> ?strict_method_semantics:bool -> unit -> config

Create a custom retry configuration.

  • parameter retry_response

    Custom predicate for response-based retry decisions

  • parameter retry_exception

    Custom predicate for exception-based retry decisions

  • parameter strict_method_semantics

    When true, raise error on non-idempotent retry

Retry Decision Functions

val should_retry : config:config -> method_:Method.t -> status:int -> bool

Check if a request should be retried based on built-in rules only. For full custom predicate support, use should_retry_response.

val should_retry_response : config:config -> method_:Method.t -> status:int -> headers:Headers.t -> bool

Check if a response should be retried, including custom predicates. Returns true if either built-in rules or custom predicate says to retry.

val should_retry_exn : config:config -> exn -> bool

Check if an exception should trigger a retry using custom predicates.

val calculate_backoff : config:config -> attempt:int -> float

Calculate backoff delay for a given attempt

val parse_retry_after : ?backoff_max:float -> string -> float option

Parse Retry-After header value (seconds or HTTP date).

Per RFC 9110 Section 10.2.3, Retry-After can be either:

  • A non-negative integer (delay in seconds)
  • An HTTP-date (absolute time to retry after)

Values are capped to backoff_max (default 120s) to prevent DoS from malicious servers specifying extremely long delays.

val with_retry : sw:Eio.Switch.t -> clock:_ Eio.Time.clock -> config:config -> f:(unit -> 'a) -> should_retry_exn:(exn -> bool) -> 'a

Execute a request with retry logic

val pp_config : Format.formatter -> config -> unit

Pretty print retry configuration

val log_retry : attempt:int -> delay:float -> reason:string -> unit

Log retry attempt information