Estive enfrentando uma queda de ~40s ao recarregar meu site. Isso está um pouco além das minhas habilidades técnicas, então pedi ajuda ao Claude.
Ele afirma que o problema é que o lançador do unicorn está enviando o sinal errado para o mestre antigo (SIGTERM em vez de SIGQUIT), o que o mata antes que o novo possa começar a atender.
A correção é aparentemente uma atualização de 6 linhas, mas gostaria de verificar se faz sentido aqui primeiro.
Clique para ver o relatório gerado pelo Claude
Resumo
No caminho do código pitchfork, on_reload_pitchfork() do config/unicorn_launcher retira o mestre antigo assim que um processo worker existe, sem nunca verificar se o novo mestre pode atender a uma solicitação, e o retira com kill (SIGTERM, desligamento imediato) em vez de kill -s QUIT (desligamento gracioso).
O caminho do unicorn imediatamente acima, on_reload_unicorn(), faz ambos corretamente — ele emite curl $LOCAL_WEB para aquecer o novo mestre e depois envia QUIT.
O resultado observado de um único sv hup unicorn é de ~40 segundos durante os quais nenhuma solicitação é concluída, terminando exatamente quando a nova geração registra Booted Rails.
Ambiente
- Discourse
2026.8.0-latest.1, Rails 8.0.5.1, Ruby 3.4.10 - pitchfork 0.18.2,
RUN_PITCHFORKnão definido (então a ramificação pitchfork está ativa) - contêiner independente, nginx →
http://127.0.0.1:3000 - 2 vCPU / 4 GB droplet; inicialização do app leva ~40s
O código
config/unicorn_launcher:
function on_reload_unicorn() {
...
while [ ... workers not up ... ]; do sleep 1; done
curl $LOCAL_WEB &>/dev/null # aquece o novo mestre
kill -s QUIT $UNICORN_PID # gracioso
}
function on_reload_pitchfork() {
log "Reloading pitchfork ($UNICORN_PID)"
OLD_PID=$UNICORN_PID
pitchfork "${PITCHFORK_ARGS[@]}" &
UNICORN_PID=$!
count=0
while [ "$count" -lt 180 -a -z "$(pgrep -f -P $UNICORN_PID worker)" ]; do
log "Waiting for new pitchfork workers under $UNICORN_PID to start up..."
count=$((count + 1))
sleep 1
done
kill $OLD_PID 2>/dev/null # SIGTERM, e sem aquecimento primeiro
}
pgrep -f -P $UNICORN_PID worker retorna assim que um processo worker é gerado. No pitchfork, o worker existe muito antes que a aplicação tenha terminado de carregar, então o loop sai precocemente e o mestre antigo é desmontado enquanto o novo ainda está inicializando.
Comportamento observado
production.log, uma recarga (carimbos de tempo UTC):
23:16:49 Completed 200 OK in 95ms <- geração de saída, quente
(44 segundos; nem uma linha Completed)
23:17:33 Booted Rails 8.0.5.1 application in production environment
23:17:36 Completed 200 OK in 4128ms <- primeiro render na nova geração, frio
23:17:39 Completed 200 OK in 200ms
Um monitor fazendo polling GET / a cada ~2s através do nginx durante essa janela registrou:
23:17:11 499 19.697s (cliente desistiu; upstream nunca respondeu)
23:17:29 503 16.301s
23:17:31 502 1.448s
e o log de erro do nginx para o 502:
2026/08/14 23:17:31 [error] 70#70: *110675 recv() failed (104: Connection reset by peer)
while reading response header from upstream, upstream: "http://127.0.0.1:3000/"
upstream_response_time é igual a request_time em cada linha com falha, então o nginx não é o gargalo — as solicitações chegam a um processo que não pode respondê-las. O reset em 23:17:31 é consistente com o SIGTERM desmontando o mestre de saída com conexões ainda em fila nele.
Isso é reproduzível: duas recargas em uma única implantação produziram duas janelas idênticas de ~40s, cada uma terminando em uma linha Booted Rails.
Não é pressão de recursos: sem OOM kills em dmesg, swap em 141 MB de 2048 usados, média de carga 0.07, e ambas as inicializações foram concluídas normalmente.
Correção sugerida
Espelhar a ramificação do unicorn — esperar até que o novo mestre realmente atenda antes de retirar o antigo, e retirá-lo de forma graciosa:
count=0
while [ "$count" -lt 180 -a -z "$(pgrep -f -P $UNICORN_PID worker)" ]; do
log "Waiting for new pitchfork workers under $UNICORN_PID to start up..."
count=$((count + 1))
sleep 1
done
- kill $OLD_PID 2>/dev/null
+ count=0
+ until curl -sf -o /dev/null "$LOCAL_WEB" || [ "$count" -ge 180 ]; do
+ log "Waiting for the new pitchfork master to serve a request..."
+ count=$((count + 1))
+ sleep 1
+ done
+
+ kill -s QUIT $OLD_PID 2>/dev/null
A metade QUIT-em-vez-de-TERM é inequívoca: pitchfork herda a semântica de sinal do unicorn, onde QUIT drena e TERM é a parada imediata.
PS: Este é meu primeiro relatório assistido por IA. Me avise se estou quebrando alguma etiqueta de qualquer forma. ![]()