Packages
oban
2.19.1
2.23.0
2.22.1
2.22.0
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.4
2.19.3
2.19.2
2.19.1
2.19.0
2.18.3
2.18.2
2.18.1
2.18.0
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.3
2.16.2
2.16.1
2.16.0
2.15.4
2.15.3
2.15.2
2.15.1
2.15.0
2.14.2
2.14.1
2.14.0
2.13.6
2.13.5
2.13.4
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.3
2.11.2
2.11.1
2.11.0
2.10.1
2.10.0
retired
2.9.2
2.9.1
2.9.0
2.8.0
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
1.2.0
1.1.0
1.0.0
1.0.0-rc.2
1.0.0-rc.1
0.12.1
0.12.0
0.11.1
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Robust job processing, backed by modern PostgreSQL, SQLite3, and MySQL.
Current section
Files
Jump to
Current section
Files
lib/oban/backoff.ex
defmodule Oban.Backoff do
@moduledoc false
@type jitter_mode :: :inc | :dec | :both
@retry_mult Application.compile_env(:oban, [Oban.Backoff, :retry_mult], 100)
@doc """
Calculate an exponential backoff in seconds for a given attempt.
By default, the exponent is clamped to a maximum of 10 to prevent unreasonably long delays.
## Examples
iex> Oban.Backoff.exponential(1)
2
iex> Oban.Backoff.exponential(1, mult: 100)
200
iex> Oban.Backoff.exponential(1, min_pad: 10)
12
iex> Oban.Backoff.exponential(10)
1024
iex> Oban.Backoff.exponential(11)
1024
"""
@spec exponential(pos_integer(), opts :: keyword()) :: number()
def exponential(attempt, opts \\ []) do
max_pow = Keyword.get(opts, :max_pow, 10)
min_pad = Keyword.get(opts, :min_pad, 0)
mult = Keyword.get(opts, :mult, 1)
min_pad + mult * Integer.pow(2, min(attempt, max_pow))
end
@doc """
Applies a random amount of jitter to the provided value.
## Examples
iex> jitter = Oban.Backoff.jitter(200)
...> jitter in 180..220
true
iex> jitter = Oban.Backoff.jitter(200, mode: :inc)
...> jitter in 200..220
true
iex> jitter = Oban.Backoff.jitter(200, mode: :dec)
...> jitter in 180..200
true
"""
@spec jitter(time :: pos_integer(), mode: jitter_mode(), mult: float()) :: number()
def jitter(time, opts \\ []) do
mode = Keyword.get(opts, :mode, :both)
mult = Keyword.get(opts, :mult, 0.1)
rand = :rand.uniform()
diff = trunc(rand * mult * time)
case mode do
:inc ->
time + diff
:dec ->
time - diff
:both ->
if rand >= 0.5 do
time + diff
else
time - diff
end
end
end
@doc """
Attempt a database interaction repeatedly until it succeeds or retries are exhausted.
Failed attempts are spaced out using exponential backoff with jitter. By default, functions are
retried _infinitely_ with a maximum of ~100 seconds between retries.
This function is designed to guard against flickering database errors and retry safety only
applies `DBConnection.ConnectionError`, `Postgrex.Error`, and `GenServer` timeouts.
## Examples
iex> Oban.Backoff.with_retry(fn -> :ok end)
:ok
iex> Oban.Backoff.with_retry(fn -> :ok end, :infinity)
:ok
iex> Oban.Backoff.with_retry(fn -> :ok end, 10)
:ok
"""
@spec with_retry((-> term()), :infinity | pos_integer()) :: term()
def with_retry(fun, retries \\ :infinity) when is_function(fun, 0) do
with_retry(fun, retries, 1)
end
@db_errors [DBConnection.ConnectionError, Postgrex.Error]
defp with_retry(fun, retries, attempt) do
fun.()
rescue
error in @db_errors ->
retry_or_raise(fun, retries, attempt, :error, error, __STACKTRACE__)
catch
:exit, {error, _} = reason when error in [:timeout | @db_errors] ->
retry_or_raise(fun, retries, attempt, :exit, reason, __STACKTRACE__)
end
@compile {:inline, retry_or_raise: 6}
defp retry_or_raise(fun, retries, attempt, kind, reason, stacktrace) do
if retries == :infinity or attempt < retries do
attempt
|> exponential(mult: @retry_mult)
|> jitter()
|> Process.sleep()
with_retry(fun, retries, attempt + 1)
else
:erlang.raise(kind, reason, stacktrace)
end
end
end