defmodule BridgeEx.Graphql.Retry do @moduledoc """ Utils for handling retrying of functions. """ @spec retry( String.t(), (any() -> {:error, String.t()} | {:ok, any()}), Keyword.t() ) :: {:ok, any()} | {:error, any()} def retry(arg, fun, retry_options) do # These values should always be present delay = Keyword.get(retry_options, :delay) policy = Keyword.get(retry_options, :policy) timing = Keyword.get(retry_options, :timing) max_retries = Keyword.get(retry_options, :max_retries) do_retry(arg, fun, policy, delay, timing, max_retries + 1) end defp do_retry(arg, fun, _retry_policy, _delay, _timing, 1), do: fun.(arg) defp do_retry(_arg, _fun, _retry_policy, _delay, _timing, n) when n <= 0, do: {:error, :invalid_retry_value} defp do_retry(arg, fun, policy, delay, timing, retries) do case fun.(arg) do {:error, reason} -> if policy.(reason) do Process.sleep(calculate_new_delay(delay, timing)) do_retry(arg, fun, policy, delay, timing, retries - 1) else {:error, reason} end val -> val end end defp calculate_new_delay(delay, :constant), do: delay defp calculate_new_delay(delay, :exponential) do delay = delay * 2 max_delta = round(delay * 0.1) shift = random_uniform(2 * max_delta) - max_delta case delay + shift do n when n <= 0 -> 0 n -> n end end defp random_uniform(n) when n <= 0, do: 0 defp random_uniform(n), do: :rand.uniform(n) end