Current section
Files
Jump to
Current section
Files
lib/retryable.ex
defmodule Retryable do
defmodule App do
use Application
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec, warn: false
# Define workers and child supervisors to be supervised
children = [
# Starts a worker by calling: Retryable.Worker.start_link(arg1, arg2, arg3)
supervisor(Retryable.WorkSupervisor, []),
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Retryable.Supervisor]
Supervisor.start_link(children, opts)
end
end
defmacro attempt(opts \\ []) do
do_block = Keyword.get(opts, :do)
rescue_block = Keyword.get(opts, :rescue)
unless do_block do
compile_error(__ENV__, "You must pass a do block")
end
unless rescue_block do
compile_error(__ENV__, "You must pass a rescue block")
end
looks_like_case_inner = is_list(rescue_block) && Enum.all?(rescue_block, fn
{:"->", _, _} -> true
_ -> false
end)
unless looks_like_case_inner do
compile_error(__ENV__, "Your rescue block must be like:\n\tresult_match_one -> response\n\tresult_match_two -> response")
end
timeout_clause =
rescue_block
|> Enum.find(fn
{:"->", _, [[timeout: _], _]} -> true
_ -> false
end)
timeout_ms = if timeout_clause do
{:"->", _, [[timeout: timeout_ms], _]} = timeout_clause
timeout_ms
else
nil
end
do_fn = quote do
fn -> unquote(do_block) end
end
rescue_fn = quote do
fn (var!(attempts), result) ->
case result do
unquote(rescue_block)
end
end
end
quote do
Retryable.attempt_work(
retry_strategy: unquote(rescue_fn),
retryable: unquote(do_fn),
notify: self,
timeout_ms: unquote(timeout_ms)
)
end
end
@doc """
Creates a new piece of work to be carried out.
"""
def attempt_work(opts \\ []) do
{:ok, pid} = Retryable.WorkSupervisor.attempt(opts)
pid
end
@doc """
Adds jitter to a retry time so that many failing jobs won't all retry at
the same time
"""
def jitter(amount_ms), do: jitter(0, amount_ms)
def jitter(value_ms, amount_ms) do
value_ms + round(:rand.uniform * amount_ms)
end
@doc """
Adds an exponential backoff to a retry time based on the number of attempts
"""
def exponential_backoff(value_ms, attempts, exp_base \\ 2) do
value_ms + round((:math.pow(exp_base, attempts) * 1000))
end
defp compile_error(caller, desc) do
raise CompileError, file: caller.file, line: caller.line, description: desc
end
end