View Source OnceMore (OnceMore v1.0.0)

Provides retry functionality with configurable backoff strategies.

OnceMore allows you to easily retry operations that may fail temporarily, such as network requests or distributed system operations. It supports both simple retries and stateful retries with an accumulator.

OnceMore is heavily inspired by the Retry library. For details on the design decisions, see Problems with Retry. If you're migrating from Retry, check out the Migrating from Retry.

Basic usage

The most common use case is retrying a function until it succeeds:

import OnceMore.DelayStreams

OnceMore.retry(
  fn -> make_network_call() end,
  &match?({:error, _}, &1),
  Stream.take(exponential_backoff(), 5)
)

Features

  • Composable backoff strategies via OnceMore.DelayStreams
  • Flexible retry predicates - specify precisely what conditions to retry
  • Accumulator support - maintain state between retry attempts

Stateful retries

For operations that need to maintain state between attempts, use retry_with_acc/4:

OnceMore.retry_with_acc(
  fn attempts -> {network_call(), attempts + 1} end,
  fn result, _attempts -> match?({:error, _}, result) end,
  0,
  Stream.take(constant_backoff(), 5)
)

Summary

Types

Accumulator used in retry_with_acc/4 function.

Non-negative integer representing milliseconds to wait between retry attempts.

Sequence of delays between retry attempts.

Return value from the function being retried.

Functions

Retries a function until it succeeds or runs out of retry attempts.

Retries a function until it succeeds or runs out of retry attempts passing accumulator between attempts.

Types

acc()

@type acc() :: term()

Accumulator used in retry_with_acc/4 function.

delay()

@type delay() :: non_neg_integer()

Non-negative integer representing milliseconds to wait between retry attempts.

delays()

@type delays() :: Enumerable.t(delay())

Sequence of delays between retry attempts.

result()

@type result() :: term()

Return value from the function being retried.

Functions

retry(function, should_retry_fn, delays)

@spec retry((-> result()), (result() -> boolean()), delays()) :: result()

Retries a function until it succeeds or runs out of retry attempts.

Takes a function to execute, a predicate function that determines if retry is needed, and a collection of delay values that determine how much time should pass between attempts.

The first attempt is made immediately. Delays between subsequent attempts are determined by the provided delays.

Parameters

  • function - Zero arity function to be called.
  • should_retry_fn - Predicate function that receives the result of function invocation. Should return a boolean/0 indicating whether retry is needed.
  • delays - An enumerable of integers representing milliseconds between retry attempts.

Returns the result of the last function invocation after retries are exhausted or success is achieved.

Examples

# Retry with exponential backoff, limited to 5 attempts
OnceMore.retry(
  fn -> network_call() end,
  &match?({:error, _}, &1),
  Stream.take(exponential_backoff(), 5)
)

# Retry only specific errors with 100ms delays, stopping after 1 second of attempts
OnceMore.retry(
  fn -> network_call() end,
  &match?({:error, reason} when reason in @reasons, &1)
  100 |> constant_backoff() |> expiry(1_000)
)

retry_with_acc(function, should_retry_fn, acc, delays)

@spec retry_with_acc(
  (acc() -> {result(), acc()}),
  (result(), acc() -> boolean()),
  acc(),
  delays()
) ::
  {result(), acc()}

Retries a function until it succeeds or runs out of retry attempts passing accumulator between attempts.

Takes a function to execute, a predicate function that determines if retry is needed, an initial accumulator value, and a collection of delay values that determine how much time should pass between attempts.

The first attempt is made immediately. Delays between subsequent attempts are determined by the provided delays.

Parameters

  • function - Function that takes an accumulator and returns a tuple of {result, new_accumulator}.
  • should_retry_fn - Predicate function that receives the result and accumulator. Should return a boolean/0 indicating whether retry is needed.
  • acc - Initial accumulator value.
  • delays - An enumerable of integers representing milliseconds between retry attempts.

Returns a tuple containing the result of the last function invocation and final accumulator value after retries are exhausted or success is achieved.

Examples

# Retry with exponential backoff, tracking attempt count
OnceMore.retry_with_acc(
  fn count -> {network_call(), count + 1} end,
  fn result, _count -> match?({:error, _}, result) end,
  0,
  Stream.take(exponential_backoff(), 5)
)

# Retry with constant backoff, accumulating errors
OnceMore.retry_with_acc(
  fn errors -> 
    case network_call() do
      {:error, reason} = err -> {err, [reason | errors]}
      success -> {success, errors}
    end
  end,
  fn result, _errors -> match?({:error, _}, result),
  [],
  Stream.take(constant_backoff(), 5)
)