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"},
]
endUsage / 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
endFAQ
How is Jobbit like Task?
Both are used to perform asynchronous tasks.
Both have
yield/2which 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.Supervisorto be running- Note:
Jobbititself can be used as a child_spec callback module instead ofTask.Supervisor. - Note:
Jobbitstarts its own task supervisor by default at application startup.
- Note:
How is Jobbit different than Task?
Jobbitnever links to the calling process. All the risk is move to the task process.Jobbitonly provides one function to (idiomatically) synchronize on a tasks result (viayield/2).Taskhas also hasyield/2which is similar, but also providesawait/2which will raise if the task times out;yeild/2will not raise.Jobbithomogenizes results of tasks. WithTaskyielding can return{:ok, :ok}.Jobbithomogenizes{:ok, :ok}into:ok. This way is much less boilerplate.
How is Jobbit different than Task.Supervisor?
Jobbitprovides a default supervisor via its application tree.Jobbitis less generalized, but easier to out-of-the-box.Jobbithas fewer functions, and a more focused scope.JobbitONLY 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_valuewas 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/2returns{:error, TimeoutError.t()}. TimeoutError is an exception (it can be raised) that wraps thetimeoutvalue.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/2returns{:error, ExitError.t()}. ExitError