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:
-
:one_for_one
- Solo restaura el proceso hijo que haya fallado. -
:one_for_all
- Restaura todos los procesos hijos en el caso de una falla. -
:rest_for_one
- Restaura todos los procesos fallidos y cualquier proceso empezado después de este.
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
-
id
- Llave requerida. Usada por el supervisor para identificar la especificación de los hijos. -
start
- Llave requerida. El módulo/función/argumentos para llamar cuando sea iniciado por el supervisor. -
shutdown
- Llave opcional. Define el comportamiento de los hijos durante el la terminación. Las opciones son:-
:brutal_kill
- Hijo es parado inmediatamente. -
cualquier entero positivo - el tiempo en milisegundos que el supervisor esperará antes de matar a un proceso hijo. Si el proceso es de tipo
:worker
este valor será por defecto 5000. -
:infinity
- El supervisor esperará indefinidamente antes de matar al proceso hijo. Por defecto para el tipo:supervisor
. No recomendado para el tipo:worker
.
-
-
restart
- Llave opcional. Hay muchos enfoques para manejar fallas en los procesos hijos:-
:permanent
- El proceso hijo siempre es restaurado. Por defecto para todos los procesos. -
:temporary
- El proceso hijo nunca es restaurado. -
:transient
- El proceso hijo es restaurado solo si es terminado de una forma anormal.
-
-
type
- Llave opcional. Los procesos pueden ser de tipo:worker
o:supervisor
, Por defecto es:worker
.
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.
¿Encontraste un error o quieres contribuir a la lección? ¡Edita esta lección en GitHub!