Cómo crear un Supervisor Tree

Un supervisor es un tipo de proceso que permite controlar automáticamente el ciclo de vida de otros procesos (o GenServers) reiniciándolos automáticamente al detectar un fallo que los tumbe.

Los supervisores en Elixir son una pieza fundamental para construir aplicaciones robustas y confiables. Nos permiten gestionar automáticamente el reinicio de procesos que fallan, siguiendo la filosofía de let it crash que caracteriza a este lenguaje. En lugar de tener que reiniciar manualmente cada proceso cuando algo sale mal, un supervisor se encarga de vigilar esos procesos y levantarlos de nuevo si se caen, manteniendo así la aplicación en funcionamiento sin intervención manual constante.

Cuando creamos procesos en Elixir, normalmente usamos GenServers con funciones como start, startLink o startMonitor. Cada una tiene un comportamiento distinto ante fallos: start simplemente arranca el proceso y si se muere, se queda muerto; startLink enlaza el proceso con el que lo lanza, de modo que si uno cae, el otro también; y startMonitor permite recibir mensajes cuando un proceso termina. Sin embargo, en entornos reales, tener que reiniciar procesos manualmente es poco práctico y propenso a errores. Aquí es donde entran los supervisores.

Un supervisor es un proceso que se encarga de lanzar y monitorizar otros procesos, llamados hijos. Si alguno de estos hijos falla, el supervisor lo detecta y lo reinicia automáticamente. Esto no solo simplifica la gestión de errores, sino que también permite que la aplicación siga funcionando sin interrupciones visibles para el usuario. Además, si un proceso se cae repetidamente en poco tiempo, el supervisor puede decidir fallar también, indicando que hay un problema más grave que requiere atención.

Para ilustrar esto, trabajamos con dos módulos: una calculadora y una pila, ambos implementados como GenServers. La calculadora realiza operaciones básicas como suma, resta, multiplicación y división, y tiene un detalle interesante: si intentamos dividir por cero, el proceso falla. Esto nos sirve para probar cómo el supervisor maneja los reinicios. La pila es una estructura de datos simple que funciona como una lista, donde podemos insertar elementos al principio y sacar elementos de la cabeza, implementando así una calculadora basada en pila.

Un cambio importante que hacemos es asignar nombres a estos GenServers usando la opción name en startLink. Esto nos permite referirnos a ellos directamente por nombre en lugar de tener que pasar sus PIDs constantemente, lo que simplifica mucho la interacción con estos procesos.

Para crear un supervisor, primero declaramos su especificación de hijos, conocida como child_spec. Esta es una lista que contiene los procesos que el supervisor debe lanzar y vigilar. Por ejemplo, podemos incluir la calculadora y la pila como hijos, indicando el módulo y los parámetros necesarios para arrancarlos. Luego, lanzamos el supervisor con Supervisor.start_link/2, pasando esta lista y opciones como la estrategia de reinicio.

La estrategia más sencilla y común es :one_for_one, que significa que si un proceso hijo falla, solo se reinicia ese proceso. Existen otras estrategias más complejas, como :one_for_all, donde si un hijo falla, se reinician todos los hijos, pero para empezar, :one_for_one es suficiente.

Al arrancar el supervisor, este lanza simultáneamente todos sus hijos. Si alguno de ellos falla, el supervisor lo detecta y lo reinicia automáticamente. Por ejemplo, si en la pila hacemos una operación inválida que provoca un error, el proceso de la pila se detiene, pero el supervisor lo vuelve a levantar para que podamos seguir usándolo sin problemas. Eso sí, hay que tener en cuenta que al reiniciarse, el estado del GenServer se resetea, por lo que no podemos asumir que la pila mantenga su contenido anterior.

Además, si un proceso hace llamadas a otro y este último falla, el primero también puede caer, lo que hace que el supervisor reinicie ambos procesos si están bajo su supervisión. Esto garantiza que el sistema se mantenga consistente y que los procesos relacionados se reinicien juntos cuando sea necesario.

En resumen, los supervisores nos permiten agrupar y gestionar múltiples procesos de forma ordenada, lanzándolos y monitorizándolos para que, ante fallos, se reinicien automáticamente y la aplicación siga funcionando. Esto es clave para construir sistemas tolerantes a fallos y que puedan recuperarse sin intervención manual. Más adelante podemos profundizar en las diferentes estrategias de supervisión y en cómo personalizar las especificaciones de los hijos para adaptarlas a nuestras necesidades.

Para que quede claro cómo se declara un supervisor con dos hijos, calculadora y pila, y se lanza con la estrategia :one_for_one, el código sería algo así:

children = [
  {Supervisame.Calculadora, []},
  {Supervisame.Pila, []}
]

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

Con esto, el supervisor se encarga de iniciar ambos GenServers y de reiniciarlos si alguno falla, manteniendo la aplicación estable y lista para seguir funcionando.

Lista de reproducción
  1. 1
    Cómo crear procesos
    11 minutos
  2. 2
    Cómo pasar mensajes entre procesos
    13 minutos
  3. 3
    Diccionario de un proceso y mantener un estado
    15 minutos
  4. 4
    Cómo enlazar procesos para detectar fallos
    13 minutos
  5. 5
    Cómo monitorizar procesos
    10 minutos
  6. 6
    ¿Qué es un GenServer?
    15 minutos
  7. 7
    Cómo enviar mensajes con un GenServer
    19 minutos
  8. 8
    Control de errores y gestión de un GenServer
    19 minutos
  9. 9
    Cómo renombrar procesos
    11 minutos
  10. 10
    Cómo crear un Supervisor Tree
    17 minutos
  11. 11
    Estrategias para trabajar con Supervisor
    18 minutos
  12. 12
    Estrategias para crear un Supervisor
    11 minutos
  13. 13
    Resumen sobre procesos OTP
    12 minutos
  14. 14
    Cómo usar Application
    12 minutos
  15. 15
    Ejemplo de Application con hijos
    14 minutos