Current section

Files

Jump to
verk lib verk.ex
Raw

lib/verk.ex

defmodule Verk do
@moduledoc """
Verk is a job processing system that integrates well with Sidekiq jobs.
Each queue will have a pool of workers handled by `poolboy` that will process jobs.
Verk has a retry mechanism similar to Sidekiq that keeps retrying the jobs with a reasonable backoff.
It has an API that provides information about the queues.
"""
alias Verk.{Job, Time, Manager}
@schedule_key "schedule"
@doc """
Add a new `queue` with a pool of size `size` of workers.
"""
@spec add_queue(atom, pos_integer) :: Supervisor.on_start_child()
def add_queue(queue, size \\ 25) when is_atom(queue) and size > 0 do
Manager.add(queue, size)
end
@doc """
Remove `queue` from the list of queues that are being processed.
"""
@spec remove_queue(atom) :: :ok | {:error, :not_found}
def remove_queue(queue) when is_atom(queue) do
Manager.remove(queue)
end
defdelegate pause_queue(queue), to: Verk.Manager, as: :pause
defdelegate resume_queue(queue), to: Verk.Manager, as: :resume
@doc """
Enqueues a Job to the specified queue returning the respective job id.
The job must have:
* a valid `queue`
* a list of `args` to perform
* a module to perform (`class`)
* a valid `jid`
Optionally a Redix server can be passed which defaults to `Verk.Redis`.
"""
@spec enqueue(%Job{}, GenServer.server()) :: {:ok, binary} | {:error, term}
def enqueue(job, redis \\ Verk.Redis)
def enqueue(job = %Job{queue: nil}, _redis), do: {:error, {:missing_queue, job}}
def enqueue(job = %Job{class: nil}, _redis), do: {:error, {:missing_module, job}}
def enqueue(job = %Job{args: args}, _redis) when not is_list(args),
do: {:error, {:missing_args, job}}
def enqueue(job = %Job{max_retry_count: nil}, redis) do
job = %Job{job | max_retry_count: Job.default_max_retry_count()}
enqueue(job, redis)
end
def enqueue(job = %Job{max_retry_count: count}, _redis) when not is_integer(count),
do: {:error, {:invalid_max_retry_count, job}}
def enqueue(job = %Job{jid: nil}, redis), do: enqueue(%Job{job | jid: generate_jid()}, redis)
def enqueue(job = %Job{jid: jid, queue: queue}, redis) do
job = %Job{job | enqueued_at: Time.now() |> DateTime.to_unix()}
case Redix.command(redis, ["LPUSH", "queue:#{queue}", Job.encode!(job)]) do
{:ok, _} -> {:ok, jid}
{:error, reason} -> {:error, reason}
end
end
@doc """
Schedules a Job to the specified queue returning the respective job id.
The job must have:
* a valid `queue`
* a list of `args` to perform
* a module to perform (`class`)
* a valid `jid`
Optionally a Redix server can be passed which defaults to `Verk.Redis`.
"""
@spec schedule(%Job{}, %DateTime{}, GenServer.server()) :: {:ok, binary} | {:error, term}
def schedule(job, datetime, redis \\ Verk.Redis)
def schedule(job = %Job{queue: nil}, %DateTime{}, _redis), do: {:error, {:missing_queue, job}}
def schedule(job = %Job{class: nil}, %DateTime{}, _redis), do: {:error, {:missing_module, job}}
def schedule(job = %Job{args: args}, %DateTime{}, _redis) when not is_list(args),
do: {:error, {:missing_args, job}}
def schedule(job = %Job{jid: nil}, perform_at = %DateTime{}, redis) do
schedule(%Job{job | jid: generate_jid()}, perform_at, redis)
end
def schedule(job = %Job{jid: jid}, perform_at = %DateTime{}, redis) do
if Time.after?(Time.now(), perform_at) do
# past time to do the job
enqueue(job, redis)
else
case Redix.command(redis, [
"ZADD",
@schedule_key,
DateTime.to_unix(perform_at),
Job.encode!(job)
]) do
{:ok, _} -> {:ok, jid}
{:error, reason} -> {:error, reason}
end
end
end
defp generate_jid do
<<part1::32, part2::32>> = :crypto.strong_rand_bytes(8)
"#{part1}#{part2}"
end
end