Options that control retry logic for calls to external services.
Retry options can be given either as this struct or as a plain keyword list
(which is validated and converted with new/1). The available options are:
:backoff- The backoff strategy used to grow the delay between retries. The default value is:exponential.:base(non_neg_integer/0) - The initial delay between retries, in milliseconds (0for no delay). The default value is10.:factor(pos_integer/0) - Growth factor applied on each retry. Only used for:linearbackoff. The default value is1.:cap(pos_integer/0) - Caps the delay between retries to at most this many milliseconds.:expiry- Time budget for the retrying, in milliseconds. Delays that fit are used as-is; the delay that would overshoot the budget is trimmed instead, so the last attempt starts exactly at the deadline rather than past it. Defaults to no time budget;:infinitystates that explicitly (see the note on unbounded retries below).:max_attempts- Maximum number of attempts, counting the initial attempt — so the default of5is one try plus four retries. Use:infinityto retry without a count bound (see the note on unbounded retries below). The default value is5.:jitter- Random jitter applied to delays.trueapplies +/- 10%; a float (e.g.0.25) applies that proportion. Helps avoid retrying in lockstep (thundering herd). The default value isfalse.:retry_on(function of arity 1) - A predicate run on the return value of the call. When it returns a truthy value the call is retried, exactly as if the function had returned:retry(the result itself is used as the retry reason, and the circuit breaker melts). Lets you drive retries from a function that was not written to return:retry/{:retry, reason}. Defaults to no predicate. An explicit:retry/{:retry, reason}return always takes precedence over the predicate. A predicate that fails — raising, throwing, or exiting rather than answering — is treated as no match, leaving the result untouched, and logs a warning.:retry_exceptions- Which raised exceptions should trigger a retry, as either a list of exception modules or a predicate run on the exception itself. A predicate can decide per instance rather than per type — useful when the same exception type is sometimes transient and sometimes not. Defaults to[], meaning raised exceptions are not retried; use:retry/{:retry, reason}return values, or the:retry_onpredicate, to drive retries instead. An exception that is not matched also does not melt the circuit breaker, and once retries are spent the original exception is re-raised with its original stacktrace. A predicate that fails — raising, throwing, or exiting rather than answering — is treated as no match, so the exception it was asked to classify reaches the caller unchanged, and a warning is logged. The default value is[].
Bounds
:max_attempts defaults to 5, so retrying always stops on its own. With the
default :base of 10 and exponential backoff that is a bound of four retries
across roughly 150ms of waiting — deliberately a safety net rather than a tuned
policy. If your dependency needs a longer retry window, raise :base (100 is
the usual choice for an HTTP service) rather than the attempt count.
Note that this bound and the circuit breaker's :tolerate interact: every
failing attempt melts the breaker, so five attempts melt five of the ten a
default breaker tolerates, and two fully-failing calls open it.
:expiry adds a time budget alongside the count; whichever is reached first
stops the retrying. See Bounding retries.
Unbounded retries
Retrying without a count bound is available, but it has to be asked for:
ExternalService.start(:my_service, retry: [max_attempts: :infinity])Be deliberate about it. A call that keeps returning :retry then keeps
retrying forever, and the circuit breaker is not a reliable backstop —
exponential backoff widens the gap between attempts until failures no longer
accumulate fast enough to open it. Pair it with an :expiry, or reserve it for
work that genuinely has nowhere else to go.
Summary
Functions
Layers a keyword list of per-call overrides onto a base struct.
Builds a validated RetryOptions struct from a keyword list (or returns an
existing struct unchanged).
Types
@type t() :: %ExternalService.RetryOptions{ backoff: :exponential | :linear, base: non_neg_integer(), cap: pos_integer() | nil, expiry: pos_integer() | :infinity | nil, factor: pos_integer(), jitter: boolean() | float(), max_attempts: pos_integer() | :infinity | nil, retry_exceptions: [module()] | (Exception.t() -> as_boolean(term())), retry_on: (term() -> as_boolean(term())) | nil }
Functions
Layers a keyword list of per-call overrides onto a base struct.
Only the keys actually present in opts are overridden; every other field is
taken from base. This is how per-call retry options tweak — rather than
reset — a service's configured defaults. A %RetryOptions{} given in place of
the keyword list replaces base wholesale, since a struct is already a
complete set of options.
Raises NimbleOptions.ValidationError if opts is invalid.
Builds a validated RetryOptions struct from a keyword list (or returns an
existing struct unchanged).
Raises NimbleOptions.ValidationError if the options are invalid.