Packages
oban
1.0.0-rc.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/config.ex
defmodule Oban.Config do
@moduledoc false
alias Oban.Crontab.Cron
use Agent
@type cronjob :: {Cron.t(), module(), Keyword.t()}
@type prune :: :disabled | {:maxlen, pos_integer()} | {:maxage, pos_integer()}
@type t :: %__MODULE__{
beats_maxage: pos_integer(),
circuit_backoff: timeout(),
crontab: [cronjob()],
dispatch_cooldown: pos_integer(),
name: atom(),
node: binary(),
poll_interval: pos_integer(),
prefix: binary(),
prune: prune(),
prune_interval: pos_integer(),
prune_limit: pos_integer(),
queues: [{atom(), pos_integer()}],
repo: module(),
rescue_after: pos_integer(),
rescue_interval: pos_integer(),
shutdown_grace_period: timeout(),
timezone: Calendar.time_zone(),
verbose: false | Logger.level()
}
@type option :: {:name, module()} | {:conf, t()}
@enforce_keys [:node, :repo]
defstruct beats_maxage: 60 * 5,
circuit_backoff: :timer.seconds(30),
crontab: [],
dispatch_cooldown: 5,
name: Oban,
node: nil,
poll_interval: :timer.seconds(1),
prefix: "public",
prune: {:maxlen, 1_000},
prune_interval: :timer.minutes(1),
prune_limit: 5_000,
queues: [],
repo: nil,
rescue_after: 60,
rescue_interval: :timer.minutes(1),
shutdown_grace_period: :timer.seconds(15),
timezone: "Etc/UTC",
verbose: false
@spec start_link([option()]) :: GenServer.on_start()
def start_link(opts) when is_list(opts) do
{conf, opts} = Keyword.pop(opts, :conf)
Agent.start_link(fn -> conf end, opts)
end
@spec new(Keyword.t()) :: t()
def new(opts) when is_list(opts) do
opts =
opts
|> Keyword.put_new(:node, node_name())
|> Keyword.put(:queues, Keyword.get(opts, :queues) || [])
|> Keyword.put(:crontab, Keyword.get(opts, :crontab) || [])
Enum.each(opts, &validate_opt!/1)
opts = Keyword.update(opts, :crontab, [], &parse_crontab/1)
struct!(__MODULE__, opts)
end
@spec get(atom()) :: t()
def get(name), do: Agent.get(name, & &1)
@spec node_name(%{optional(binary()) => binary()}) :: binary()
def node_name(env \\ System.get_env()) do
cond do
Node.alive?() ->
to_string(node())
Map.has_key?(env, "DYNO") ->
Map.get(env, "DYNO")
true ->
:inet.gethostname()
|> elem(1)
|> to_string()
end
end
defp validate_opt!({:beats_maxage, maxage}) do
unless is_integer(maxage) and maxage > 60 do
raise ArgumentError, "expected :beats_maxage to be an integer greater than 60"
end
end
defp validate_opt!({:circuit_backoff, interval}) do
unless is_integer(interval) and interval > 0 do
raise ArgumentError, "expected :circuit_backoff to be a positive integer"
end
end
defp validate_opt!({:crontab, crontab}) do
unless is_list(crontab) and Enum.all?(crontab, &valid_crontab?/1) do
raise ArgumentError,
"expected :crontab to be a list of {expression, worker} or " <>
"{expression, worker, options} tuples"
end
end
defp validate_opt!({:dispatch_cooldown, period}) do
unless is_integer(period) and period > 0 do
raise ArgumentError, "expected :dispatch_cooldown to be a positive integer"
end
end
defp validate_opt!({:name, name}) do
unless is_atom(name) do
raise ArgumentError, "expected :name to be a module or atom"
end
end
defp validate_opt!({:node, node}) do
unless is_binary(node) and node != "" do
raise ArgumentError, "expected :node to be a non-empty binary"
end
end
defp validate_opt!({:poll_interval, interval}) do
unless is_integer(interval) and interval > 0 do
raise ArgumentError, "expected :poll_interval to be a positive integer"
end
end
defp validate_opt!({:prefix, prefix}) do
unless is_binary(prefix) and Regex.match?(~r/^[a-z0-9_]+$/i, prefix) do
raise ArgumentError, "expected :prefix to be a binary with alphanumeric characters"
end
end
defp validate_opt!({:prune, mode}) do
case mode do
:disabled -> :ok
{:maxlen, len} when is_integer(len) and len > 0 -> :ok
{:maxage, age} when is_integer(age) and age > 0 -> :ok
_ -> raise ArgumentError, "unexpected :prune mode, #{inspect(mode)}"
end
end
defp validate_opt!({:prune_interval, interval}) do
unless is_integer(interval) and interval > 0 do
raise ArgumentError, "expected :prune_interval to be a positive integer"
end
end
defp validate_opt!({:prune_limit, limit}) do
unless is_integer(limit) and limit > 0 do
raise ArgumentError, "expected :prune_interval to be a positive integer"
end
end
defp validate_opt!({:queues, queues}) do
unless Keyword.keyword?(queues) and Enum.all?(queues, &valid_queue?/1) do
raise ArgumentError, "expected :queues to be a keyword list of {atom, integer} pairs"
end
end
defp validate_opt!({:repo, repo}) do
unless Code.ensure_compiled?(repo) and function_exported?(repo, :__adapter__, 0) do
raise ArgumentError, "expected :repo to be an Ecto.Repo"
end
end
defp validate_opt!({:rescue_after, interval}) do
unless is_integer(interval) and interval > 0 do
raise ArgumentError, "expected :rescue_after to be a positive integer"
end
end
defp validate_opt!({:rescue_interval, interval}) do
unless is_integer(interval) and interval > 0 do
raise ArgumentError, "expected :rescue_interval to be a positive integer"
end
end
defp validate_opt!({:shutdown_grace_period, interval}) do
unless is_integer(interval) and interval > 0 do
raise ArgumentError, "expected :shutdown_grace_period to be a positive integer"
end
end
defp validate_opt!({:timezone, timezone}) do
unless is_binary(timezone) and match?({:ok, _}, DateTime.now(timezone)) do
raise ArgumentError, "expected :timezone to be a known timezone"
end
end
defp validate_opt!({:verbose, verbose}) do
unless verbose in ~w(false error warn info debug)a do
raise ArgumentError, "expected :verbose to be `false` or a log level"
end
end
defp validate_opt!(option) do
raise ArgumentError, "unknown option provided #{inspect(option)}"
end
defp valid_crontab?({expression, worker}) do
valid_crontab?({expression, worker, []})
end
defp valid_crontab?({expression, worker, options}) do
is_binary(expression) and
Code.ensure_loaded?(worker) and
function_exported?(worker, :perform, 2) and
Keyword.keyword?(options)
end
defp valid_crontab?(_crontab), do: false
defp valid_queue?({_name, limit}), do: is_integer(limit) and limit > 0
defp parse_crontab(crontab) do
for tuple <- crontab do
case tuple do
{expression, worker} ->
{Cron.parse!(expression), worker, []}
{expression, worker, options} ->
{Cron.parse!(expression), worker, options}
end
end
end
end