Packages
exq
0.17.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.0
0.14.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.2
0.12.1
0.12.0
0.11.0
0.10.1
0.10.0
0.9.1
0.9.0
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.0.2
Exq is a job processing library compatible with Resque / Sidekiq for the Elixir language.
Current section
Files
Jump to
Current section
Files
lib/exq/worker/server.ex
defmodule Exq.Worker.Server do
@moduledoc """
Worker process is responsible for the parsing and execution of a Job.
It then broadcasts results to Stats / Manager.
Currently uses the `terminate` callback to track job success/failure.
## Initialization:
* `job_serialized` - Full JSON payload of the Job.
* `manager` - Manager process pid.
* `queue` - The queue the job came from.
* `stats` - Stats process pid.
* `namespace` - Redis namespace
* `host` - Host name
Expects :work message after initialization to kickoff work.
"""
use GenServer
alias Exq.Middleware.Server, as: Middleware
alias Exq.Middleware.Pipeline
alias Exq.Worker.Metadata
defmodule State do
defstruct job_serialized: nil,
manager: nil,
queue: nil,
namespace: nil,
stats: nil,
host: nil,
redis: nil,
middleware: nil,
pipeline: nil,
metadata: nil,
middleware_state: nil
end
def start_link(
job_serialized,
manager,
queue,
stats,
namespace,
host,
redis,
middleware,
metadata
) do
GenServer.start_link(
__MODULE__,
{job_serialized, manager, queue, stats, namespace, host, redis, middleware, metadata},
[]
)
end
@doc """
Kickoff work associated with worker.
"""
def work(pid) do
GenServer.cast(pid, :work)
end
## ===========================================================
## GenServer callbacks
## ===========================================================
def init({job_serialized, manager, queue, stats, namespace, host, redis, middleware, metadata}) do
{
:ok,
%State{
job_serialized: job_serialized,
manager: manager,
queue: queue,
stats: stats,
namespace: namespace,
host: host,
redis: redis,
middleware: middleware,
metadata: metadata
}
}
end
@doc """
Kickoff work associated with worker.
This step handles:
* Parsing of JSON object
* Preparation of target module
Calls :dispatch to then call target module.
"""
def handle_cast(:work, state) do
state = %{state | middleware_state: Middleware.all(state.middleware)}
state = %{state | pipeline: before_work(state)}
case state |> Map.fetch!(:pipeline) |> Map.get(:terminated, false) do
# case done to run the after hooks
true -> nil
_ -> GenServer.cast(self(), :dispatch)
end
{:noreply, state}
end
# Dispatch work to the target module (call :perform method of target).
def handle_cast(:dispatch, state) do
dispatch_work(
state.pipeline.assigns.worker_module,
state.pipeline.assigns.job,
state.metadata
)
{:noreply, state}
end
# Worker done with normal termination message.
def handle_cast({:done, result}, state) do
state =
if !has_pipeline_after_work_ran?(state.pipeline) do
%{state | pipeline: pipeline_after_processed_work(state, result)}
else
state
end
{:stop, :normal, state}
end
def handle_info({:DOWN, _, _, _, :normal}, state) do
state =
if !has_pipeline_after_work_ran?(state.pipeline) do
error = "Worker shutdown"
%{state | pipeline: pipeline_after_failed_work(state, error, error)}
else
state
end
{:stop, :normal, state}
end
def handle_info({:DOWN, _, :process, _, error}, state) do
error_message =
error
|> Inspect.Algebra.to_doc(%Inspect.Opts{})
|> Inspect.Algebra.format(%Inspect.Opts{}.width)
|> to_string
state =
if !has_pipeline_after_work_ran?(state.pipeline) do
%{state | pipeline: pipeline_after_failed_work(state, error_message, error)}
else
state
end
{:stop, :normal, state}
end
def handle_info(_info, state) do
{:noreply, state}
end
## ===========================================================
## Internal Functions
## ===========================================================
def dispatch_work(worker_module, job, metadata) do
# trap exit so that link can still track dispatch without crashing
Process.flag(:trap_exit, true)
worker = self()
{:ok, pid} =
Task.start_link(fn ->
:ok = Metadata.associate(metadata, self(), job)
result = apply(worker_module, :perform, job.args)
GenServer.cast(worker, {:done, result})
end)
Process.monitor(pid)
end
defp before_work(state) do
%Pipeline{event: :before_work, worker_pid: self()}
|> Pipeline.assign_worker_state(state)
|> Pipeline.chain(state.middleware_state)
end
defp pipeline_after_processed_work(state, result) do
%Pipeline{event: :after_processed_work, worker_pid: self(), assigns: state.pipeline.assigns}
|> Pipeline.assign(:result, result)
|> Pipeline.chain(state.middleware_state)
end
defp pipeline_after_failed_work(state, error_message, error) do
%Pipeline{event: :after_failed_work, worker_pid: self(), assigns: state.pipeline.assigns}
|> Pipeline.assign(:error_message, error_message)
|> Pipeline.assign(:error, error)
|> Pipeline.chain(state.middleware_state)
end
defp has_pipeline_after_work_ran?(pipeline) do
Map.has_key?(pipeline, :result) || Map.has_key?(pipeline, :error)
end
end