Supervisores OTP

Los supervisores son procesos especializados con un propósito: monitorear otros procesos. Estos supervisores nos permiten crear aplicaciones tolerantes a fallos que automáticamente restauren procesos hijos en caso de falla.

Configuración

La magia de los supervisores esta en la función Supervisor.start_link/2. Adicionalmente a iniciar nuestro supervisor e hijos esto nos permiten definir la estrategia que nuestro supervisor va a usar para administrar los procesos hijos.

Usando SimpleQueue de la lección OTP Concurrency, vamos a empezar:

Crea un nuevo proyecto usando mix new simple_queue --sup para que se cree usando un árbol de supervisión. El código para el módulo SimpleQueue debería ir en lib/simple_queue.ex y el código del supervisor sera agregado en lib/simple_queue/application.ex.

Los hijos están definidos usando una lista, ya sea una lista con los nombres de los 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

o una lista de tuplas si quieres agregar opciones de configuración:

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

Si ejecutamos iex -S mix veremos que nuestra SimpleQueue es automáticamente iniciada:

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

Si nuestro proceso SimpleQueue fuera a romperse o ser terminado nuestro supervisor automáticamente los restauraría como si nada hubiera pasado.

Estrategias

Hay actualmente 3 diferentes estrategias disponibles para los supervisores:

Especificación de hijo

Después de que el supervisor ha comenzado este debe saber como comenzar/parar/restaurar a sus hijos. Cada módulo hijo debería tener una función child_spec/1 que defina estos comportamientos. Los macros use GenServer, use Supervisor, y use Agent automáticamente definen este método por nosotros (SimpleQueue tiene use Genserver, entonces no necesitamos modificar el módulo), pero si necesitas definirlo por ti mismo child_spec/1 debería retornar un mapa de opciones:

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

DynamicSupervisor

Los supervisores normalmente empiezan con una lista de hijos para iniciar cuando empieza la aplicación. Como sea algunas veces los hijos supervisados no serán conocidos cuando nuestra aplicación empieza (Por ejemplo puede que tengamos una aplicación web que inicia un nuevo proceso para manejar a un usuario conectándose a nuestro sitio). Para esos casos vamos a querer un supervisor donde los hijos pueden ser iniciados a demanda. El supervidor dinámico es usado para manejar este caso.

Como no especificamos los hijos solo necesitamos definir las opciones de ejecución del supervisor. El supervidor dinámico solo soporta la estrategia :one_for_one:

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

DynamicSupervisor.start_link(options)

Luego para empezar un SimpleQueue dinámicamente usaremos start_child/2 el cual toma un supervisor y la especificación del hijo(Como SimpleQueue usa use GenServer la especificación ya está definida):

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

Supervidor de tareas

Las tareas tienen su propio supervisor especializado, Task.Supervisor. Diseñado para crear tareas dinámicamente, el supervisor usa DynamicSupervisor por debajo.

Preparación

Incluir Task.Supervisor no es diferente a otros supervisores:

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

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

La mayor diferencia entre Supervisor y Task.Supervisor es que este tiene la estrategia por defecto :temporary (las tareas nunca serán restauradas).

Tareas supervisadas

Con el supervisor iniciado podemos usar la función start_child/2 para crear tareas supervisadas:

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

Si nuestra tarea falla prematuramente esto la restaurará por nosotros. Este puede ser particularmente útil cuando se trabaja con conexiones entrantes o trabajo procesado en segundo plano.

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