Task Supervisor
Mix.install([
{:jason, "~> 1.4"},
{:kino, "~> 0.8.0", override: true},
{:youtube, github: "brooklinjazz/youtube"},
{:hidden_cell, github: "brooklinjazz/hidden_cell"}
])
Navigation
Setup
Ensure you type the ea keyboard shortcut to evaluate all Elixir cells before starting. Alternatively you can evaluate the Elixir cells as you read.
Review Questions
- Why might we want to supervise a task?
- How do we supervise tasks?
Task Supervisor
We can start Task processes under a Task.Supervisor which can dynamically supervise tasks. The supervisor will automatically restart tasks if they encounter an error.
Generally speaking, we should start Task processes under a supervisor.
> We encourage developers to rely on supervised tasks as much as possible. Supervised tasks improves the visibility of how many tasks are running at a given moment and enable a huge variety of patterns that gives you explicit control on how to handle the results, errors, and timeouts. Here is a summary:
>
> Using Task.Supervisor.start_child/2 allows you to start a fire-and-forget task that you don’t care about its results or if it completes successfully or not.
>
> Using Task.Supervisor.async/2 + Task.await/2 allows you to execute tasks concurrently and retrieve its result. If the task fails, the caller will also fail.
>
> Using Task.Supervisor.async_nolink/2 + Task.yield/2 + Task.shutdown/2 allows you to execute tasks concurrently and retrieve their results or the reason they failed within a given time frame. If the task fails, the caller won’t fail. You will receive the error reason either on yield or shutdown.
>
> * Hexdocs: Dynamically Supervised Tasks
The Task.Supervisor is started as a child under a normal supervisor. We start the Task.Supervisor as a named process using an atom or a module name.
children = [
{Task.Supervisor, name: MyTaskSupervisor}
]
{:ok, supervisor_pid} =
Supervisor.start_link(children, strategy: :one_for_one, name: :parent_supervisor)
We don’t need to define a MyTaskSupervisor module. The supervisor uses this name to start a Task.Supervisor process. We can see that MyTaskSupervisor is a child process of our supervisor.
Supervisor.which_children(supervisor_pid)
We can also see that demonstrated in the following diagram.
Kino.Process.sup_tree(supervisor_pid)
Async Tasks
Now we can spawn supervised Task processes under MyTaskSupervisor using Task.Supervisor.async/3.
task =
Task.Supervisor.async(MyTaskSupervisor, fn ->
IO.puts("Task Started")
Process.sleep(1000)
IO.puts("Task Finished")
"response!"
end)
Task.await(task)
We can spawn many tasks under the supervisor.
tasks =
Enum.map(1..5, fn int ->
task =
Task.Supervisor.async(MyTaskSupervisor, fn ->
IO.puts("Task Started")
Process.sleep(30000)
IO.puts("Task Finished")
int * 2
end)
end)
Evaluate the diagram below to see the tasks spawned under the MyTaskSupervisor process.
Kino.Process.sup_tree(supervisor_pid)
We can then await the response from all of the tasks.
Task.await_many(tasks)
Fire-and-Forget Tasks
Task.Supervisor.start_child/2 allows us to start a fire-and-forget task that will perform some work without returning a response.
Task.Supervisor.start_child(MyTaskSupervisor, fn ->
IO.puts("Fire-and-forget task started")
Process.sleep(60000)
IO.puts("Fire-and-forget task finished")
end)
Re-evaluate the cell above a few times, and you’ll see several tasks under the MyTaskSupervisor.
children = Supervisor.which_children(MyTaskSupervisor)
We can provide a :restart strategy when we start a process. By default, Task.Supervisor.start_child/2 uses the :temporary :restart strategy. These Task processes will never be restarted.
{:ok, pid} =
Task.Supervisor.start_child(MyTaskSupervisor, fn ->
Process.sleep(60000)
end)
Supervisor.which_children(MyTaskSupervisor)
|> IO.inspect(label: "Started children")
Process.exit(pid, :kill)
Process.sleep(1000)
Supervisor.which_children(MyTaskSupervisor)
|> IO.inspect(label: "Children after exit")
Instead we can use the :permanent process to always restart a Task or :transient to restart a Task if it’s exit reason is not :normal, :shutdown, or {:shutdown, reason}.
See Task.Supervisor.start_child/3#options for more.
Now when we kill a Task started with the :transient strategy, notice that a new process with a different pid is started under MyTaskSupervisor.
{:ok, pid} =
Task.Supervisor.start_child(
MyTaskSupervisor,
fn ->
Process.sleep(60000)
end,
restart: :transient
)
Supervisor.which_children(MyTaskSupervisor)
|> IO.inspect(label: "Started children")
Process.exit(pid, :kill)
Process.sleep(1000)
Supervisor.which_children(MyTaskSupervisor)
|> IO.inspect(label: "Children after exit")
Further Reading
Consider the following resource(s) to deepen your understanding of the topic.
Mark As Completed
file_name = Path.basename(Regex.replace(~r/#.+/, __ENV__.file, ""), ".livemd")
save_name =
case Path.basename(__DIR__) do
"reading" -> "task_supervisor_reading"
"exercises" -> "task_supervisor_exercise"
end
progress_path = __DIR__ <> "/../progress.json"
existing_progress = File.read!(progress_path) |> Jason.decode!()
default = Map.get(existing_progress, save_name, false)
form =
Kino.Control.form(
[
completed: input = Kino.Input.checkbox("Mark As Completed", default: default)
],
report_changes: true
)
Task.async(fn ->
for %{data: %{completed: completed}} <- Kino.Control.stream(form) do
File.write!(
progress_path,
Jason.encode!(Map.put(existing_progress, save_name, completed), pretty: true)
)
end
end)
form
Commit Your Progress
Run the following in your command line from the curriculum folder to track and save your progress in a Git commit.
Ensure that you do not already have undesired or unrelated changes by running git status or by checking the source control tab in Visual Studio Code.
$ git checkout -b task-supervisor-reading
$ git add .
$ git commit -m "finish task supervisor reading"
$ git push origin task-supervisor-reading
Create a pull request from your task-supervisor-reading branch to your solutions branch.
Please do not create a pull request to the DockYard Academy repository as this will spam our PR tracker.
DockYard Academy Students Only:
Notify your teacher by including @BrooklinJazz in your PR description to get feedback.
You (or your teacher) may merge your PR into your solutions branch after review.
If you are interested in joining the next academy cohort, sign up here to receive more news when it is available.
Up Next
| Previous | Next |
|---|---|
| Task | Tasks |