Supervisão
O supervisor gerencia ciclos de vida de serviços, tratando ordenação de inicialização, reinicializações automáticas e encerramento gracioso. Serviços com auto_start: true são iniciados quando a aplicação inicia.
Configuração de Ciclo de Vida
Serviços se registram com o supervisor usando um bloco lifecycle. Para processos, use process.service para encapsular uma definição de processo:
# 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 precisa referenciar um host de processos configurado. A entrada em requires precisa resolver para outro serviço supervisionado ou, por meio da extração de dependências do registro, para um serviço supervisionado que seja proprietário do recurso referenciado.
| Campo | Padrão | Descrição |
|---|---|---|
auto_start |
false |
Inicia automaticamente quando supervisor inicia |
startup |
required |
Política de inicialização de uma raiz automática: required bloqueia o boot em caso de falha; optional pode falhar e continuar tentando sem bloquear ramos independentes |
start_timeout |
10s |
Tempo máximo permitido para inicialização |
stop_timeout |
10s |
Tempo máximo para encerramento gracioso |
stable_threshold |
5s |
Tempo de execução antes do serviço ser considerado estável |
requires |
[] |
Serviços que devem estar executando primeiro (alias legado: depends_on) |
startup |
required |
required reporta um auto-start falho ou bloqueado como erro de transação; optional deixa o serviço continuar tentando em segundo plano sem falhar o lote |
Resolução de Dependências
O supervisor resolve dependências de duas fontes:
- Dependências explícitas declaradas em
requires(ou o legadodepends_on) - Dependências extraídas do registro de referências de entradas (ex:
database: app:dbna sua configuração)
graph LR
A[HTTP Server] --> B[Router]
B --> C[Handler Function]
C --> D[Database]
C --> E[Cache]
Dependências iniciam antes dos dependentes. Se o Serviço C depende de A e B, ambos A e B devem alcançar o estado Running antes de C iniciar.
requires quando a extração de dependências do registro consegue rastrear essa referência até um serviço supervisionado. Use requires para dependências de ciclo de vida que ainda não estejam expressas por referências de entradas.
Política de Reinicialização
Quando um serviço falha, o supervisor tenta novamente de acordo com seu bloco restart:
lifecycle:
restart:
initial_delay: 1s # First retry wait
max_delay: 90s # Accepted backoff cap; see current behavior below
backoff_factor: 2.0 # Accepted multiplier; see current behavior below
jitter: 0.1 # ±10% randomization
max_attempts: 0 # 0 = infinite retries
No runtime v0.3.32a, o supervisor cria uma nova calculadora de backoff para cada tentativa e usa somente seu primeiro intervalo. Portanto, cada nova tentativa aguarda initial_delay com o jitter configurado — 0,9s a 1,1s para os valores acima. backoff_factor e max_delay são campos de configuração aceitos, mas não alteram esse cronograma no runtime fixado.
max_attempts conta a primeira inicialização que falhou. O valor 1 não permite nova tentativa, 10 permite no máximo nove inicializações posteriores e 0 permite tentativas ilimitadas.
Quando um serviço executa por mais tempo que stable_threshold, o contador de tentativas reseta. Isso previne que falhas transitórias escalem delays permanentemente.
Erros Terminais
Estes erros param tentativas de retry:
- Cancelamento de contexto
- Requisição de terminação explícita
- Erros marcados como não-retentáveis
Contexto de Segurança
Serviços podem executar com uma identidade de segurança 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
O contexto de segurança define:
| Campo | Descrição |
|---|---|
actor.id |
String de identidade para este serviço |
actor.meta |
Metadados chave-valor (role, permissões, etc.) |
groups |
Grupos de políticas a aplicar |
policies |
Políticas individuais a aplicar |
Código executando no serviço herda este contexto de segurança. O módulo security pode então verificar permissões:
local security = require("security")
if security.can("delete", "users") then
-- allowed
end
Reregistro e Substituição
Uma mudança no registry pode reregistrar um ID que já tem um controller em execução. Se o registro carrega a mesma instância de serviço, nada é perturbado. Se carrega uma instância diferente — o manager reconstruiu o serviço porque sua configuração mudou — o supervisor aposenta o controller existente e adota o substituto.
A aposentadoria abrange mais que o serviço isolado. Um dependente em execução capturou a instância substituída, então não pode continuar rodando contra um serviço que está sendo trocado por baixo dele; o fecho de aposentadoria é o serviço substituído mais todo serviço em execução que depende dele, parados em ordem de dependência (dependentes primeiro). Serviços já parados não são parados uma segunda vez — um manager que para sua própria instância antes de reregistrar não recebe um Stop redundante.
A transferência é transacional:
- O plano é computado sem tocar em nada, então uma falha de planejamento deixa o conjunto em execução intacto.
- O lote de paradas é executado. Se qualquer parada falhar, a transferência é rejeitada: os serviços que o lote já parou são reerguidos e o erro é reportado. Um serviço que não pôde ser reerguido é nomeado nesse erro. O supervisor termina possuindo o mesmo conjunto em execução que tinha antes do commit, nunca um parcialmente aposentado.
- Somente depois de o lote ter sucesso os controllers aposentados são descartados e cancelados, liberando as instâncias de serviço substituídas.
- O substituto é criado e iniciado através do mesmo sequenciador ciente de dependências de qualquer outro início, e os dependentes que foram parados para a transferência voltam a subir contra a instância adotada.
Um serviço que estava em execução antes da substituição é reiniciado depois dela mesmo quando o novo registro define auto_start: false — substituir um serviço ativo é uma atualização, não uma parada implícita. Reiniciar um dependente parado é regido por sua própria política de reinicialização e não bloqueia o commit.
Estados de Serviço
stateDiagram-v2
[*] --> Unknown
Unknown --> Starting
Starting --> Running
Running --> Stopping
Stopping --> Stopped
Stopping --> Failed : timeout/cancel
Stopped --> [*]
Running --> Failed
Starting --> Failed
Failed --> Starting : retry
Running --> Exited
Starting --> Exited
Exited --> [*]
O supervisor transiciona serviços através destes estados:
| Estado | Descrição |
|---|---|
Unknown |
Registrado mas não iniciado |
Starting |
Inicialização em progresso |
Running |
Operando normalmente |
Stopping |
Encerramento gracioso em progresso |
Stopped |
Terminado de forma limpa |
Exited |
Terminado por requisição explícita ou por um erro terminal/não recuperável |
Failed |
Erro ocorreu, pode tentar novamente |
Ordem de Inicialização e Encerramento
Inicialização: Dependências primeiro, depois dependentes. Serviços no mesmo nível de dependência podem iniciar em paralelo.
Encerramento: Dependentes primeiro, depois dependências. Isso garante que serviços dependentes terminem antes de suas dependências pararem.
Inicialização: database → cache → handler → http_server
Encerramento: http_server → handler → cache → database
Em SIGINT ou SIGTERM, o runtime inicia um encerramento gracioso e a sequência inteira roda sob um único orçamento, shutdown.timeout na configuração do runtime (padrão 30s). Esse orçamento é um prazo novo que não herda o contexto interrompido, então um Ctrl-C não corta o encerramento dos componentes; o stop_timeout por serviço continua limitando cada parada individual dentro dele. Um segundo sinal pula a sequência e sai imediatamente.
# .wippy.yaml
shutdown:
timeout: 60s
Veja Também
- Modelo de Processos - Ciclo de vida de processos
- Configuração - Formato de configuração YAML
- Módulo Security - Verificações de permissão em Lua