Packages

Background processing application/library written in Elixir that uses RabbitMQ as a messaging backend

Current section

Files

Jump to
carguero_task_bunny lib task_bunny worker_supervisor.ex
Raw

lib/task_bunny/worker_supervisor.ex

defmodule TaskBunny.WorkerSupervisor do
@moduledoc """
Supervises all TaskBunny workers.
You don't have to call or start the Supervisor explicity.
It will be automatically started by application and
configure child workers based on configuration file.
It also provides `graceful_halt/1` and `graceful_halt/2` that allow
you to shutdown the worker processes safely.
"""
use Supervisor
alias TaskBunny.{Config, Worker}
@doc false
@spec start_link(atom) :: {:ok, pid} | {:error, term}
def start_link(name \\ __MODULE__) do
Supervisor.start_link(__MODULE__, [], name: name)
end
@doc false
@spec init(list) :: {:ok, {:supervisor.sup_flags(), [Supervisor.Spec.spec()]}} | :ignore
def init([]) do
Config.workers()
|> Enum.map(fn config ->
worker(
Worker,
[config],
id: "task_bunny.worker.#{config[:queue]}"
)
end)
|> supervise(strategy: :one_for_one)
end
@doc """
Halts the job pocessing on workers gracefully.
It makes workers to stop processing new jobs and waits for jobs currently running to finish.
Note: It doesn't terminate any worker processes.
The worker and worker supervisor processes will continue existing but won't consume any new messages.
To resume it, terminate the worker supervisor then the main supervisor will start new processes.
"""
@spec graceful_halt(pid | nil, integer) :: :ok | {:error, any}
# When pid is not found. Assume it's already gone.
def graceful_halt(nil, _timeout), do: :ok
def graceful_halt(pid, timeout) do
workers =
pid
|> Supervisor.which_children()
|> Enum.map(fn {_, child, _, _} -> child end)
|> Enum.filter(fn child -> is_pid(child) end)
Enum.each(workers, fn worker ->
Worker.stop_consumer(worker)
end)
case wait_for_all_jobs_done(workers, timeout) do
true -> :ok
false -> {:error, "Worker is busy"}
end
end
@doc """
Similar to graceful_halt/2 but gets pid from module name.
"""
@spec graceful_halt(integer) :: :ok | {:error, any}
def graceful_halt(timeout) do
pid = Process.whereis(__MODULE__)
graceful_halt(pid, timeout)
end
defp wait_for_all_jobs_done(workers, timeout) do
Enum.find_value(0..round(timeout / 50), fn _ ->
if workers_running?(workers) do
:timer.sleep(50)
false
else
true
end
end) || false
end
defp workers_running?(workers) do
workers
|> Enum.any?(fn pid -> worker_running?(pid) end)
end
defp worker_running?(pid) when is_pid(pid) do
%{runners: runners, consuming: consuming} = GenServer.call(pid, :status)
runners > 0 || consuming
end
defp worker_running?(_), do: false
end