Packages
oban
2.13.0
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/engine.ex
defmodule Oban.Engine do
@moduledoc """
Defines an Engine for job orchestration.
Engines are responsible for all non-plugin database interaction, from inserting through
executing jobs.
Oban ships with two Engine implementations:
1. `Basic` — The default engine for development, production, and manual testing mode.
2. `Inline` — Designed specifically for testing, it executes jobs immediately, in-memory, as
they are inserted.
> #### 🌟 SmartEngine {: .info}
>
> The Basic engine lacks advanced functionality such as global limits, rate limits, and
> unique bulk insert. For those features and more, see the [`SmartEngine` in Oban
> Pro](https://getoban.pro/docs/pro/smart_engine.html).
"""
alias Ecto.{Changeset, Multi}
alias Oban.{Config, Job}
@type conf :: Config.t()
@type job :: Job.t()
@type meta :: map()
@type opts :: Keyword.t()
@type queryable :: Ecto.Queryable.t()
@type running :: map()
@type seconds :: pos_integer()
@doc """
Initialize metadata for a queue engine.
Queue metadata is used to identify and track subsequent actions such as fetching or staging
jobs.
"""
@callback init(conf(), opts()) :: {:ok, meta()} | {:error, term()}
@doc """
Store the given key/value pair in the engine meta.
"""
@callback put_meta(conf(), meta(), atom(), term()) :: meta()
@doc """
Format engine meta in a digestible format for queue inspection.
"""
@callback check_meta(conf(), meta(), running()) :: map()
@doc """
Refresh a queue to indicate that it is still alive.
"""
@callback refresh(conf(), meta()) :: meta()
@doc """
Prepare a queue engine for shutdown.
The queue process is expected to stop processing new jobs after shutdown starts, though it may
continue executing jobs that are already running.
"""
@callback shutdown(conf(), meta()) :: meta()
@doc """
Insert a job into the database.
"""
@callback insert_job(conf(), Job.changeset(), opts()) :: {:ok, Job.t()} | {:error, term()}
@doc """
Insert a job within an `Ecto.Multi`.
"""
@callback insert_job(conf(), Multi.t(), Multi.name(), Oban.changeset_or_fun(), opts()) ::
Multi.t()
@doc """
Insert multiple jobs into the database.
"""
@callback insert_all_jobs(conf(), Oban.changesets_or_wrapper(), opts()) :: [Job.t()]
@doc """
Insert multiple jobs within an `Ecto.Multi`
"""
@callback insert_all_jobs(
conf(),
Multi.t(),
Multi.name(),
Oban.changesets_or_wrapper_or_fun(),
opts()
) :: Multi.t()
@doc """
Fetch available jobs for the given queue, up to configured limits.
"""
@callback fetch_jobs(conf(), meta(), running()) :: {:ok, {meta(), [Job.t()]}} | {:error, term()}
@doc """
Record that a job completed successfully.
"""
@callback complete_job(conf(), Job.t()) :: :ok
@doc """
Transition a job to `discarded` and record an optional reason that it shouldn't be ran again.
"""
@callback discard_job(conf(), Job.t()) :: :ok
@doc """
Record an executing job's errors and either retry or discard it, depending on whether it has
exhausted its available attempts.
"""
@callback error_job(conf(), Job.t(), seconds()) :: :ok
@doc """
Reschedule an executing job to run some number of seconds in the future.
"""
@callback snooze_job(conf(), Job.t(), seconds()) :: :ok
@doc """
Mark an `executing`, `available`, `scheduled` or `retryable` job as `cancelled` to prevent it
from running.
"""
@callback cancel_job(conf(), Job.t()) :: :ok
@doc """
Mark many `executing`, `available`, `scheduled` or `retryable` job as `cancelled` to prevent them
from running.
"""
@callback cancel_all_jobs(conf(), queryable()) :: {:ok, {non_neg_integer(), [Job.t()]}}
@doc """
Mark a job as `available`, adding attempts if already maxed out. If the job is currently
`available`, `executing` or `scheduled` it should be ignored.
"""
@callback retry_job(conf(), Job.t()) :: :ok
@doc """
Mark many jobs as `available`, adding attempts if already maxed out. Any jobs currently
`available`, `executing` or `scheduled` should be ignored.
"""
@callback retry_all_jobs(conf(), queryable()) :: {:ok, non_neg_integer()}
@doc false
def init(%Config{} = conf, [_ | _] = opts) do
with_span(:init, conf, fn engine ->
engine.init(conf, opts)
end)
end
@doc false
def refresh(%Config{} = conf, %{} = meta) do
with_span(:refresh, conf, fn engine ->
engine.refresh(conf, meta)
end)
end
@doc false
def shutdown(%Config{} = conf, %{} = meta) do
with_span(:shutdown, conf, fn engine ->
engine.shutdown(conf, meta)
end)
end
@doc false
def put_meta(%Config{} = conf, %{} = meta, key, value) when is_atom(key) do
with_span(:put_meta, conf, fn engine ->
engine.put_meta(conf, meta, key, value)
end)
end
@doc false
def check_meta(%Config{} = conf, %{} = meta, %{} = running) do
with_span(:check_meta, conf, fn engine ->
engine.check_meta(conf, meta, running)
end)
end
@doc false
def insert_job(%Config{} = conf, %Changeset{} = changeset, opts) do
meta = %{changeset: changeset, opts: opts}
with_span(:insert_job, conf, meta, fn engine ->
engine.insert_job(conf, changeset, opts)
end)
end
@doc false
def insert_job(%Config{} = conf, %Multi{} = multi, name, changeset, opts) do
meta = %{changeset: changeset, opts: opts}
with_span(:insert_job, conf, meta, fn engine ->
engine.insert_job(conf, multi, name, changeset, opts)
end)
end
@doc false
def insert_all_jobs(%Config{} = conf, changesets, opts) do
meta = %{changesets: changesets, opts: opts}
with_span(:insert_all_jobs, conf, meta, fn engine ->
engine.insert_all_jobs(conf, changesets, opts)
end)
end
@doc false
def insert_all_jobs(%Config{} = conf, %Multi{} = multi, name, changesets, opts) do
meta = %{changesets: changesets, opts: opts}
with_span(:insert_all_jobs, conf, meta, fn engine ->
engine.insert_all_jobs(conf, multi, name, changesets, opts)
end)
end
@doc false
def fetch_jobs(%Config{} = conf, %{} = meta, %{} = running) do
with_span(:fetch_jobs, conf, fn engine ->
engine.fetch_jobs(conf, meta, running)
end)
end
@doc false
def complete_job(%Config{} = conf, %Job{} = job) do
with_span(:complete_job, conf, %{job: job}, fn engine ->
engine.complete_job(conf, job)
end)
end
@doc false
def discard_job(%Config{} = conf, %Job{} = job) do
with_span(:discard_job, conf, %{job: job}, fn engine ->
engine.discard_job(conf, job)
end)
end
@doc false
def error_job(%Config{} = conf, %Job{} = job, seconds) when is_integer(seconds) do
with_span(:error_job, conf, %{job: job}, fn engine ->
engine.error_job(conf, job, seconds)
end)
end
@doc false
def snooze_job(%Config{} = conf, %Job{} = job, seconds) when is_integer(seconds) do
with_span(:snooze_job, conf, %{job: job}, fn engine ->
engine.snooze_job(conf, job, seconds)
end)
end
@doc false
def cancel_job(%Config{} = conf, %Job{} = job) do
with_span(:cancel_job, conf, %{job: job}, fn engine ->
engine.cancel_job(conf, job)
end)
end
@doc false
def cancel_all_jobs(%Config{} = conf, queryable) do
with_span(:cancel_all_jobs, conf, fn engine ->
engine.cancel_all_jobs(conf, queryable)
end)
end
@doc false
def retry_job(%Config{} = conf, %Job{} = job) do
with_span(:retry_job, conf, %{job: job}, fn engine ->
engine.retry_job(conf, job)
end)
end
@doc false
def retry_all_jobs(%Config{} = conf, queryable) do
with_span(:retry_all_jobs, conf, fn engine ->
engine.retry_all_jobs(conf, queryable)
end)
end
defp with_span(event, %Config{} = conf, tele_meta \\ %{}, fun) do
tele_meta = Map.merge(tele_meta, %{conf: conf, engine: conf.engine})
:telemetry.span([:oban, :engine, event], tele_meta, fn ->
engine = Config.get_engine(conf)
{fun.(engine), tele_meta}
end)
end
end