Current section

Files

Jump to
honeydew lib honeydew failure_mode exponential_retry.ex
Raw

lib/honeydew/failure_mode/exponential_retry.ex

defmodule Honeydew.FailureMode.ExponentialRetry do
alias Honeydew.Job
alias Honeydew.FailureMode.Retry
alias Honeydew.FailureMode.Move
@moduledoc """
Instructs Honeydew to retry a job a number of times on failure, waiting an exponentially growing number
of seconds between retry attempts. You may specify the base of exponential delay with the `:base` argument,
it defaults to 2.
Please note, this failure mode will not work with the ErlangQueue queue implementation at the moment.
## Examples
Retry jobs in this queue 3 times, delaying exponentially between with a base of 2:
```elixir
Honeydew.start_queue(:my_queue, failure_mode: {#{inspect __MODULE__},
times: 3,
base: 2})
```
Retry jobs in this queue 3 times, delaying exponentially between with a base of 2, and then move to another queue:
```elixir
Honeydew.start_queue(:my_queue,
failure_mode: {#{inspect __MODULE__},
times: 3,
base: 2,
finally: {#{inspect Move},
queue: :dead_letters}})
```
"""
require Logger
@behaviour Honeydew.FailureMode
@impl true
def validate_args!(args) when is_list(args) do
args
|> Enum.into(%{})
|> validate_args!
end
def validate_args!(%{base: base}) when not is_integer(base) or base <= 0 do
raise ArgumentError, "You provided a bad `:base` argument (#{inspect base}) to the ExponentialRetry failure mode, it's expecting a positive number."
end
def validate_args!(args), do: Retry.validate_args!(args, __MODULE__)
@impl true
def handle_failure(job, reason, args) do
args =
args
|> Keyword.put(:fun, &exponential/3)
|> Keyword.put_new(:base, 2)
Retry.handle_failure(job, reason, args)
end
#
# base ^ times_retried - 1, rounded to integer
#
def exponential(%Job{failure_private: nil} = job, reason, args) do
exponential(%Job{job | failure_private: 0}, reason, args)
end
def exponential(%Job{failure_private: times_retried} = job, reason, %{times: max_retries, base: base}) when times_retried < max_retries do
delay_secs = (:math.pow(base, times_retried) - 1) |> round()
Logger.info "Job failed because #{inspect reason}, retrying #{max_retries - times_retried} more times, next attempt in #{delay_secs}s, job: #{inspect job}"
{:cont, times_retried + 1, delay_secs}
end
def exponential(_, _, _), do: :halt
end