Do you want to pick up from where you left of?
Take me there

Supervisores OTP

Supervisores são processos especializados com um propósito: monitorar outros processos. Estes supervisores nos possibilitam a criação de aplicações tolerantes a falhas automaticamente reiniciando processos filhos quando eles falham.

Configuração

A magia de Supervisores está na função Supervisor.start_link/2. Além de iniciar nosso supervisor e filhos, nos permite definir a estratégia que nosso supervisor irá usar para gerenciar os processos filhos.

Usando o SimpleQueue da lição OTP Concurrency vamos começar:

Crie um novo projeto usando mix new simple_queue --sup para criar uma nova árvore de supervisão. O código para o módulo SimpleQueue deve ir em lib/simple_queue.ex e o código do supervisor que nós vamos adicionar vai em lib/simple_queue/application.ex

Filhos são definidos usando uma lista, pode ser uma lista com nome de módulos:

defmodule SimpleQueue.Application do
  use Application

  def start(_type, _args) do
    children = [
      SimpleQueue
    ]

    opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

ou uma lista de tuplas se você deseja incluir opções de configuração:

defmodule SimpleQueue.Application do
  use Application

  def start(_type, _args) do
    children = [
      {SimpleQueue, [1, 2, 3]}
    ]

    opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Se nós rodarmos iex -S mix nós vamos ver que nosso SimpleQueue é automaticamente iniciado:

iex> SimpleQueue.queue
[1, 2, 3]

Se o nosso SimpleQueue fosse falhar ou ser encerrado, nosso Supervisor iria automaticamente reiniciar este processo como se nada tivesse acontecido.

Estratégias

Atualmente, existem três estratégias diferentes de reinicialização disponíveis aos supervisores:

Especificação dos filhos

Depois que o supervisor iniciou, ele deve saber como iniciar/parar/reiniciar seus filhos. Cada módulo filho deve ter uma função child_spec/2 para definir esses comportamentos. Os macros use GenServer, use Supervisor e use Agent automaticamente definem esse método para nós (SimpleQueue usa use GenServer, então nós não precisamos modificar o módulo), mas se você precisar definir você mesmo child_spec/1 deve return um map de opções:

def child_spec(opts) do
  %{
    id: SimpleQueue,
    start: {__MODULE__, :start_link, [opts]},
    shutdown: 5_000,
    restart: :permanent,
    type: :worker
  }
end

DynamicSupervisor

Supervisores normalmente começam com uma lista de filhos para iniciar quando a aplicação inicia. No entanto, às vezes os filhos supervisionados não vão ser conhecidos quando a aplicação inicia (por exemplo, nós podemos ter uma aplicação web que inicia um processo para lidar com a conexão de um usuário em nosso site). Para esses caso nós vamos querer um supervisor que os filhos podem ser iniciados sob demanda. O DynamicSupervisor é usado para lidar com esse caso.

Já que nós não vamos especificar os filhos, nós precisamos apenas definir as opções de tempo de execução do supervisor. O DynamicSupervisor suporta apenas a estratégia de supervisão :one_for_one:

options = [
  name: SimpleQueue.Supervisor,
  strategy: :one_for_one
]

DynamicSupervisor.start_link(options)

Então, para iniciar um novo SimpleQueue dinamicamente nós vamos usar start_child/2 que recebe um supervisor e a especificação do filho (de novo, SimpleQueue usa use GenServer então a especificação do filho já é definida):

{:ok, pid} = DynamicSupervisor.start_child(SimpleQueue.Supervisor, SimpleQueue)

Supervisor de tarefas

Tarefas têm o seu próprio Supervisor especializado, o Task.Supervisor. Projetado para tarefas criadas dinamicamente, o supervisor usa DynamicSupervisor por debaixo dos panos.

Instalação

Incluir o Task.Supervisor não é diferente de outros supervisores:

children = [
  {Task.Supervisor, name: ExampleApp.TaskSupervisor, restart: :transient}
]

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

A maior diferença entre Supervisor e Task.Supervisor é que a estratégia de reinício padrão é :temporary (tarefas nunca irão ser reiniciadas).

Tarefas Supervisionadas

Com o supervisor inicializado, podemos usar a função start_child/2 para criar uma tarefa supervisionada:

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

Se a nossa tarefa quebrar prematuramente, ela irá ser reiniciada para nós. Isto pode ser particularmente útil quando se trabalha com conexões de entrada ou processamento em background.

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!