Fork me on GitHub

OTP Supervisors

Supervisors are specialized processes with one purpose: monitoring other processes. These supervisors enable us to create fault-tolerant applications by automatically restarting child processes when they fail.

Table of Contents

Configuration

The magic of Supervisors is in the Supervisor.start_link/2 function. In addition to starting our supervisor and children, it allows us to define the strategy our supervisor uses for managing child processes.

Children are defined using a list and the worker/3 function we imported from Supervisor.Spec. The worker/3 function takes a module, arguments, and a set of options. Under the hood worker/3 calls start_link/3 with our arguments during initialization.

Using the SimpleQueue from the OTP Concurrency lesson let’s get started:

import Supervisor.Spec

children = [
  worker(SimpleQueue, [], [name: SimpleQueue])
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

If our process were to crash or be terminated our Supervisor would automatically restart it as if nothing had happened.

Strategies

There are currently four different restart strategies available to supervisors:

Restart values

There are several approaches for handling child process crashes:

It’s not a required option, by default it’s :permanent.

Nesting

In addition to worker processes, we can also supervise supervisors to create a supervisor tree. The only difference to us is swapping supervisor/3 for worker/3:

import Supervisor.Spec

children = [
  supervisor(ExampleApp.ConnectionSupervisor, [[name: ExampleApp.ConnectionSupervisor]]),
  worker(SimpleQueue, [[], [name: SimpleQueue]])
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

Task Supervisor

Tasks have their own specialized Supervisor, the Task.Supervisor. Designed for dynamically created tasks, the supervisor uses :simple_one_for_one under the hood.

Setup

Including the Task.Supervisor is no different than other supervisors:

import Supervisor.Spec

children = [
  supervisor(Task.Supervisor, [[name: ExampleApp.TaskSupervisor, restart: :transient]]),
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

The major difference between Supervisor and Task.Supervisor is that its default restart strategy is :temporary (tasks would never be restarted).

Supervised Tasks

With the supervisor started we can use the start_child/2 function to create a supervised task:

{:ok, pid} = Task.Supervisor.start_child(ExampleApp.TaskSupervisor, fn -> background_work end)

If our task crashes prematurely it will be re-started for us. This can be particularly useful when working with incoming connections or processing background work.


Share This Page