Supervisión
El supervisor gestiona el inicio de servicios, el orden de dependencias, los reinicios y el apagado ordenado. Los servicios con auto_start: true se inician al arrancar la aplicación.
Configuración del ciclo de vida
Los servicios se registran en el supervisor mediante un bloque lifecycle. Para procesos, usa process.service para envolver una definición de proceso:
# Process definition (the code)
- name: worker_process
kind: process.lua
source: file://worker.lua
method: main
# Supervised service (wraps the process with lifecycle management)
- name: worker
kind: process.service
process: app:worker_process
host: app:processes
lifecycle:
auto_start: true
startup: required
start_timeout: 30s
stop_timeout: 10s
stable_threshold: 5s
requires:
- app:database
restart:
initial_delay: 2s
max_delay: 60s
max_attempts: 10
host debe referenciar un process host configurado. La entrada de requires debe resolver a otro servicio supervisado o, mediante la extracción de dependencias del registro, a un servicio supervisado propietario del recurso referenciado.
| Campo | Predeterminado | Descripción |
|---|---|---|
auto_start |
false |
Iniciar automáticamente cuando el supervisor inicia |
start_timeout |
10s |
Tiempo máximo permitido para inicio |
stop_timeout |
10s |
Tiempo máximo para apagado graceful |
stable_threshold |
5s |
Tiempo de ejecución antes de considerarse estable |
requires |
[] |
Servicios que deben estar ejecutándose primero (alias heredado: depends_on) |
startup |
required |
required reporta un auto-arranque fallido o bloqueado como un error de transacción; optional deja que el servicio siga reintentando en segundo plano sin hacer fallar el lote |
Resolución de dependencias
El supervisor resuelve dependencias desde dos fuentes:
- Dependencias explícitas declaradas en
requires(o el heredadodepends_on) - Dependencias extraídas del registro desde referencias de entrada (ej.,
database: app:dben su config)
graph LR
A[HTTP Server] --> B[Router]
B --> C[Handler Function]
C --> D[Database]
C --> E[Cache]
Las dependencias se inician antes que sus dependents. Si el servicio C depende de A y B, ambas deben alcanzar el estado Running antes de iniciar C.
requires cuando la extracción de dependencias del registro puede seguirla hasta un servicio supervisado. Usa requires para dependencias de ciclo de vida que no estén ya expresadas por referencias de entradas.
Policy de restart
Cuando un servicio falla, el supervisor reintenta según su bloque restart:
lifecycle:
restart:
initial_delay: 1s # Espera del primer reintento
max_delay: 90s # Tope máximo de delay
backoff_factor: 2.0 # Multiplicador de delay por intento
jitter: 0.1 # ±10% de aleatorización
max_attempts: 0 # 0 = reintentos infinitos
| Intento | Delay Base | Con Jitter (±10%) |
|---|---|---|
| 1 | 1s | 0.9s - 1.1s |
| 2 | 2s | 1.8s - 2.2s |
| 3 | 4s | 3.6s - 4.4s |
| 4 | 8s | 7.2s - 8.8s |
| ... | ... | ... |
| N | 90s | 81s - 99s (tope) |
max_attempts cuenta el inicio fallido inicial. Un valor de 1 no permite reintentos y 10 permite como máximo nueve inicios posteriores. 0 permite intentos ilimitados.
Cuando un servicio se ejecuta más que stable_threshold, su contador de reintentos se reinicia y los fallos posteriores vuelven al delay inicial.
Errores terminales
Estos errores detienen los reintentos:
- Cancelación del contexto
- Solicitud explícita de terminación
- Errores marcados como no retryable
Contexto de seguridad
Los servicios pueden ejecutarse con una identidad de seguridad específica:
# Process definition
- name: admin_worker_process
kind: process.lua
source: file://admin_worker.lua
method: main
# Supervised service with security context
- name: admin_worker
kind: process.service
process: app:admin_worker_process
host: app:processes
lifecycle:
auto_start: true
security:
actor:
id: "service:admin-worker"
meta:
role: admin
groups:
- app:admin_policies
policies:
- app:data_access
El contexto de seguridad define:
| Campo | Descripción |
|---|---|
actor.id |
Cadena de identidad del servicio |
actor.meta |
Metadatos key-value (rol, permisos, etc.) |
groups |
Grupos de policies que se aplican |
policies |
Policies individuales que se aplican |
El código del servicio hereda este contexto de seguridad. El módulo security puede usarlo para comprobar permisos:
local security = require("security")
if security.can("delete", "users") then
-- allowed
end
Reregistro y Reemplazo
Un cambio en el registro puede volver a registrar un ID que ya tiene un controlador en ejecución. Si el registro lleva la misma instancia de servicio, nada se altera. Si lleva una instancia distinta — el manager reconstruyó el servicio porque su configuración cambió — el supervisor retira el controlador existente y adopta el reemplazo.
El retiro abarca más que ese único servicio. Un dependiente en ejecución capturó la instancia sustituida, por lo que no puede seguir ejecutándose contra un servicio que se está reemplazando por debajo; la clausura de retiro es el servicio reemplazado más todos los servicios en ejecución que dependen de él, detenidos en orden de dependencia (dependientes primero). Los servicios ya detenidos no se detienen una segunda vez — un manager que detiene su propia instancia antes de volver a registrarla no recibe un Stop redundante.
El traspaso es transaccional:
- El plan se calcula sin tocar nada, de modo que un fallo de planificación deja intacto el conjunto en ejecución.
- Se ejecuta el lote de detenciones. Si alguna detención falla, el traspaso se rechaza: los servicios que el lote ya detuvo se vuelven a levantar y se reporta el error. Un servicio que no pudo volver a levantarse se nombra en ese error. El supervisor termina siendo dueño del mismo conjunto en ejecución que tenía antes del commit, nunca de uno a medio retirar.
- Solo después de que el lote tiene éxito se descartan y cancelan los controladores retirados, liberando las instancias de servicio sustituidas.
- El reemplazo se crea e inicia a través del mismo secuenciador consciente de dependencias que cualquier otro inicio, y los dependientes que se detuvieron para el traspaso vuelven a levantarse contra la instancia adoptada.
Un servicio que estaba en ejecución antes del reemplazo se reinicia después, incluso cuando el nuevo registro establece auto_start: false — reemplazar un servicio activo es una actualización, no una detención implícita. Reiniciar un dependiente detenido se rige por su propia política de reinicio y no condiciona el commit.
Estados del Servicio
stateDiagram-v2
[*] --> Unknown
Unknown --> Starting
Starting --> Running
Running --> Stopping
Stopping --> Stopped
Stopping --> Failed : timeout/cancel
Stopped --> [*]
Running --> Failed
Starting --> Failed
Failed --> Starting : reintentar
Running --> Exited
Starting --> Exited
Exited --> [*]
El supervisor hace pasar los servicios por estos estados:
| Estado | Descripción |
|---|---|
Unknown |
Registrado pero no iniciado |
Starting |
Inicio en progreso |
Running |
Operando normalmente |
Stopping |
Apagado graceful en progreso |
Stopped |
Terminado limpiamente |
Exited |
Terminado por petición explícita o por un error no reintentable/terminal |
Failed |
Ocurrió un error, puede reintentar |
Orden de inicio y apagado :id=orden-de-startup-y-shutdown
Inicio: las dependencias se inician antes que los dependientes. Los servicios del mismo nivel de dependencias pueden iniciarse en paralelo.
Apagado: los dependientes se detienen antes que las dependencias, lo que les permite terminar primero.
Inicio: database → cache → handler → http_server
Apagado: http_server → handler → cache → database
Con SIGINT o SIGTERM el runtime comienza un apagado graceful y toda la secuencia se ejecuta bajo un único presupuesto, shutdown.timeout en la configuración del runtime (30s por defecto). Ese presupuesto es un plazo nuevo que no hereda el contexto interrumpido, por lo que un Ctrl-C no corta el apagado de los componentes; el stop_timeout por servicio sigue acotando cada detención individual dentro de él. Una segunda señal omite la secuencia y sale inmediatamente.
# .wippy.yaml
shutdown:
timeout: 60s
Véase también
- Modelo de procesos — Ciclo de vida de procesos
- Configuración — Formato de configuración YAML
- Módulo Security — Comprobaciones de permisos en Lua