Je rencontre une interruption d’environ 40 secondes lors du rechargement de mon site. C’est un peu au-delà de mes compétences techniques, donc j’ai demandé de l’aide à Claude.
Il affirme que le problème vient du fait que le lanceur Unicorn envoie le mauvais signal à l’ancien processus maître (SIGTERM au lieu de SIGQUIT), ce qui le tue avant que le nouveau puisse commencer à servir les requêtes.
La correction semble être une mise à jour de 6 lignes, mais je voudrais la vérifier ici d’abord.
Cliquez pour voir le rapport généré par Claude
Résumé
Dans la branche de code pitchfork, la fonction on_reload_pitchfork() de config/unicorn_launcher met hors service le processus maître sortant dès qu’un processus worker existe, sans jamais vérifier que le nouveau maître peut servir une requête, et il le met hors service avec kill (SIGTERM, arrêt immédiat) plutôt qu’avec kill -s QUIT (arrêt propre).
La branche unicorn juste au-dessus, on_reload_unicorn(), fait les deux correctement — elle exécute curl $LOCAL_WEB pour réchauffer le nouveau maître, puis envoie QUIT.
Le résultat observé d’un seul sv hup unicorn est une interruption d’environ 40 secondes pendant laquelle aucune requête n’est complétée, se terminant exactement lorsque la nouvelle génération journalise Booted Rails.
Environnement
- Discourse
2026.8.0-latest.1, Rails 8.0.5.1, Ruby 3.4.10 - pitchfork 0.18.2,
RUN_PITCHFORKnon défini (donc la branche pitchfork est active) - conteneur autonome, nginx →
http://127.0.0.1:3000 - 2 vCPU / 4 Go de droplet ; le démarrage de l’application prend ~40s
Le code
config/unicorn_launcher :
function on_reload_unicorn() {
...
while [ ... workers not up ... ]; do sleep 1; done
curl $LOCAL_WEB &>/dev/null # warm the new master
kill -s QUIT $UNICORN_PID # graceful
}
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, and no warm-up first
}
pgrep -f -P $UNICORN_PID worker retourne dès qu’un processus worker est créé. Avec pitchfork, le worker existe bien avant que l’application n’ait fini de charger, donc la boucle se termine prématurément et l’ancien maître est détruit pendant que le nouveau est encore en cours de démarrage.
Comportement observé
production.log, un rechargement (horodatages UTC) :
23:16:49 Completed 200 OK in 95ms <- outgoing generation, warm
(44 seconds; not one Completed line)
23:17:33 Booted Rails 8.0.5.1 application in production environment
23:17:36 Completed 200 OK in 4128ms <- first render on the new generation, cold
23:17:39 Completed 200 OK in 200ms
Un moniteur interrogeant GET / toutes les ~2s via nginx pendant cette fenêtre a enregistré :
23:17:11 499 19.697s (client gave up; upstream never answered)
23:17:29 503 16.301s
23:17:31 502 1.448s
et le journal d’erreurs de nginx pour le 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 est égal à request_time dans chaque ligne en échec, donc nginx n’est pas le goulot d’étranglement — les requêtes atteignent un processus qui ne peut pas y répondre. La réinitialisation à 23:17:31 est cohérente avec SIGTERM détruisant le maître sortant avec des connexions toujours en attente dessus.
Ceci est reproductible : deux rechargements dans un déploiement ont produit deux fenêtres identiques d’environ 40s, chacune se terminant sur une ligne Booted Rails.
Pas de pression sur les ressources : pas de tuages OOM dans dmesg, swap à 141 Mo sur 2048 utilisés, moyenne de charge 0.07, et les deux démarrages se sont terminés normalement.
Correction suggérée
Mimer la branche unicorn — attendre que le nouveau maître serve réellement avant de mettre hors service l’ancien, et le mettre hors service proprement :
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
La moitié QUIT-au-lieu-de-TERM est sans ambiguïté : pitchfork hérite de la sémantique des signaux d’unicorn, où QUIT vid les connexions et TERM est l’arrêt immédiat.
PS : C’est mon premier rapport assisté par IA. Faites-moi savoir si je viole l’étiquette d’une manière ou d’une autre. ![]()