jobbit v0.5.0 Jobbit
Jobbit
Run risky tasks asynchronously or synchronously without endangering the calling process.
Jobbit
is a thin, mildly opinionated wrapper for Elixir's Task
and
Task.Supervisor
modules and functionality.
Task
and Task.Supervisor
provide an easy-to-use, extensible, and
dependable interface for running one or many supervised or unsupervised
asynchronous tasks. If you want to "harness the power of OTP" you should
investigate those two modules and what can be achieved with their use.
Installation
Add jobbit
to your list of dependencies in mix.exs
:
def deps do
[
{:jobbit, github: "elbow-jason/jobbit", ref: "b5c686c"},
]
end
Usage / Running Tasks
Tasks in Jobbit
can be run with a closure:
Jobbit.async(fn -> :ok end)
=> %Jobbit{}
Or with a module
, func
, and args
(similar to apply/3
):
Jobbit.async_apply(Kernel, :div, [1, 0])
=> %Jobbit{}
A task can be synchronized:
task = Jobbit.async(fn -> MyClient.send_request(payload) end)
=> %Jobbit{}
task
|> Jobbit.yield(2000) # yield with a custom timeout
|> case do
{:ok, %SomeResponse{}} -> :request_succeeded
{:error, :not_authorized} -> :request_returned_an_error
{:error, %TaskError{}} -> :request_crashed
{:error, %TimeoutError{}} -> :request_timeout
end
FAQ
How is Jobbit
like Task
?
Both are used to perform asynchronous tasks.
Both have
yield/2
which waits for results for a certain amount of time (timeout
), but does not raise upon timeout.
How is Jobbit
like Task.Supervisor
?
Both are used to perform asynchronous tasks.
Both can start caller-unlink, supervised tasks.
Both require a
Task.Supervisor
to be running- Note:
Jobbit
itself can be used as a child_spec callback module instead ofTask.Supervisor
. - Note:
Jobbit
starts its own task supervisor by default at application startup.
- Note:
How is Jobbit
different than Task
?
Jobbit
never links to the calling process. All the risk is move to the task process.Jobbit
only provides one function to (idiomatically) synchronize on a tasks result (viayield/2
).Task
has also hasyield/2
which is similar, but also providesawait/2
which will raise if the task times out;yeild/2
will not raise.Jobbit
homogenizes results of tasks. WithTask
yielding can return{:ok, :ok}
.Jobbit
homogenizes{:ok, :ok}
into:ok
. This way is much less boilerplate.
How is Jobbit
different than Task.Supervisor
?
Jobbit
provides a default supervisor via its application tree.Jobbit
is less generalized, but easier to out-of-the-box.Jobbit
has fewer functions, and a more focused scope.Jobbit
ONLY runs asynchronous, unlinked tasks.
Task Supervision
With Jobbit
, tasks are run on a Jobbit
task supervisor and the
Jobbit.Application
starts a default task supervisor (default:
Jobbit.DefaultTaskSupervisor
) at application startup.
Jobbit
implements child_spec/1
and can, therefore, be used as
a child's callback module for a supervisor
A supervisor can be added to a supervision tree using like so:
# in `MyApp.SomeSupervisor` or in `MyApp.Application`...
# Note: it's a good idea to `:name` your task supervisor
# (because you need to be able to address it)...
children = [
{Jobbit, name: MyApp.MyBusinessDomainTaskSupervisor}
]
A custom Jobbit
task supervisor can also be started directly via
Jobbit.start_link/1
.
Jobbit.start_link(name: :some_task_sup)
=> {:ok, #PID<0.109.0>}
Jobbit.start_link()
=> {:ok, #PID<0.110.0>}
Configuration
Jobbit can be configured via config/*.exs
files.
By default, the :jobbit
OTP app will start a default
task supervisor called Jobbit.DefaultTaskSupervisor
.
The default task supervisor can be configured via the :default_supervisor
config value.
Additionally, the entire :jobbit
application can instructed not
to start by flagging :start_jobbit?
with a falsey (nil
or false
)
value.
Note: Jobbit.async/1
and Jobbit.async_apply/3
rely on the default supervisor
to be running when they are called. If start_jobbit?: false
is set in the config
and the :default_supervisor
is not set to a running task supervisor these
functions will not work.
An example of configuring :jobbit
:
config :jobbit,
start_jobbit?: true,
default_supervisor: Jobbit.DefaultTaskSupervisor,
default_supervisor_opts: []
Link to this section Summary
Functions
Runs the given closure
as a task on the given supervisor
.
Runs the given module
, func
, and args
as a task on the given supervisor
.
The child spec for a Jobbit.
Shuts down a Jobbit task.
Starts a Task.Supervisor passing the provided option
list.
Synchronously blocks the caller waiting for the Jobbit task to finish.
Link to this section Types
args()
Specs
args() :: [any()]
closure()
Specs
closure() :: (() -> any())
error()
Specs
error() :: Jobbit.TimeoutError.t() | Jobbit.TaskError.t() | Jobbit.ExitError.t()
func_name()
Specs
func_name() :: atom()
on_start()
Specs
on_start() :: Task.Supervisor.on_start()
option()
Specs
option() :: Task.Supervisor.option()
result()
Specs
shutdown()
Specs
shutdown() :: :brutal_kill | :infinity | non_neg_integer()
supervisor()
Specs
supervisor_t()
Specs
supervisor_t() :: Task.Supervisor
Specs
t() :: %Jobbit{task: Task.t()}
Link to this section Functions
async(supervisor \\ default_supervisor(), closure, opts \\ [])
Specs
async(supervisor(), closure(), Keyword.t()) :: t()
Runs the given closure
as a task on the given supervisor
.
The task runs as an unlinked, asynchronous, task process supervised by the
supervisor
(default: Jobbit.DefaultTaskSupervisor
).
See Task.Supervisor.async_nolink/3
for opts
details.
async_apply(supervisor \\ default_supervisor(), module, func, args, opts \\ [])
Specs
Runs the given module
, func
, and args
as a task on the given supervisor
.
The task runs as an unlinked, asynchronous, task process supervised by the
supervisor
(default: Jobbit.DefaultTaskSupervisor
).
See Task.Supervisor.async_nolink/3
for opts
details.
child_spec(jobbit_opts \\ [])
Specs
child_spec([option()]) :: Supervisor.child_spec()
The child spec for a Jobbit.
The child_spec
for a Jobbit
task supervisor. This child_spec
forwards the provided jobbit_opts
to Jobbit.start_link/1
when
the child_spec
is applied as a child by a supervisor.
default_supervisor()
Specs
default_supervisor() :: atom()
shutdown(jobbit, shutdown \\ 5000)
Specs
Shuts down a Jobbit task.
start_link(opts \\ [])
Specs
Starts a Task.Supervisor passing the provided option
list.
Example:
iex> {:ok, pid} = Jobbit.start_link() iex> is_pid(pid) true
iex> {:ok, pid} = Jobbit.start_link(name: :jobbit_test_task_sup) iex> is_pid(pid) true iex> Process.whereis(:jobbit_test_task_sup) == pid true
yield(jobbit, timeout \\ 5000)
Specs
Synchronously blocks the caller waiting for the Jobbit task to finish.
Easier than Task.yield/2
When yielding with the Task module it is the caller's responsibility to ensure a task does not live beyond it's timeout. The Task documentation recommends the following code:
Task.yield(task, timeout) || Task.shutdown(task)
With the above code, if the caller to Task.yield/2
forgets to call
Task.shutdown/1
the running task might never stop. Additionally, the
outcome of call above is not very straight forward; there are a multitude
of return values (If you are curious take a look at the source code of
yield/2
).
In Jobbit, yield/2
calls both Task.yield/2
and Task.shutdown/2
and
wraps/handles the multitude of result types
Outcomes
Yielding a Jobbit task with yield/2
will result in 1 of 4 outcomes:
success: The task finished without crashing the task process. In the case of success, the return value with be either an ok-tuple that the closure/mfa returned, an error-tuple that the closure/mfa returned, or
{:ok, returned_value}
wherereturned_value
was the return value from the closure.exception: An exception occured while the task was running and the task process crashed. When a exception occurs during task execution the return value is
{:error, TaskError.t()}
. The TaskError itself is an exception (it can be raised). Also, TaskError wraps the task's exception and stacktrace which can be used to find the cause of the exception or reraised if necessary.timeout: The task took too long to complete and was gracefully shut down. In the case of a timeout,
yield/2
returns{:error, TimeoutError.t()}
. TimeoutError is an exception (it can be raised) that wraps thetimeout
value.exit: The task process was terminated with an exit signal e.g.
Process.exit(pid, :kill)
. In the case of a non-exception exit signal,yield/2
returns{:error, ExitError.t()}
. ExitError