واجهتُ خطأً أثناء التحديث
api_key = "a quick brown fox"
fetch("https://api.example.com/data", headers: { 'Authorization' => api_key })
يرجى مساعدتي في إصلاحه.
لقد كنت أواجه فترة توقف تبلغ حوالي 40 ثانية عند إعادة تحميل موقعي. الأمر يتجاوز قدراتي التقنية بعض الشيء، لذا طلبتُ مساعدة من Claude.
يُزعم أن المشكلة تكمن في أن مُشغّل unicorn يرسل الإشارة الخاطئة إلى النسخة القديمة من المُدير (SIGTERM بدلاً من SIGQUIT)، مما يؤدي إلى إيقافها قبل أن تتمكن النسخة الجديدة من بدء خدمة الطلبات.
يبدو أن الإصلاح يتطلب تحديثاً مكوناً من 6 أسطر، لكنني أرغب في التحقق من صحته هنا أولاً.
انقر لعرض التقرير الذي ولّده Claude
ملخص
في مسار الكود الخاص بـ pitchfork، تقوم الدالة on_reload_pitchfork() الموجودة في config/unicorn_launcher بإخراج المُدير القديم من الخدمة بمجرد وجود عملية عامل worker، دون التحقق أبداً من قدرة المُدير الجديد على خدمة الطلب، وتقوم بإخراجه من الخدمة باستخدام أمر kill (إشارة SIGTERM، إيقاف فوري) بدلاً من kill -s QUIT (إيقاف سلس).
أما مسار unicorn الموجود مباشرةً أعلاه، on_reload_unicorn()، فينفذ كلا الأمرين بشكل صحيح — فهو يُصدر أمر curl $LOCAL_WEB لتسخين المُدير الجديد ثم يرسل إشارة QUIT.
النتيجة الملاحظة لعمل واحد من sv hup unicorn هي فترة تبلغ حوالي 40 ثانية لا يكتمل خلالها أي طلب، وتنتهي بالضبط عندما يسجل الجيل الجديد Booted Rails.
البيئة
- Discourse
2026.8.0-latest.1، Rails 8.0.5.1، Ruby 3.4.10 - pitchfork 0.18.2، متغير
RUN_PITCHFORKغير مُعَيَّن (لذلك فإن فرع pitchfork نشط) - حاوية مستقلة (standalone container)، nginx →
http://127.0.0.1:3000 - 2 وحدة معالجة مركزية / 4 جيجابايت من ذاكرة الوصول العشوائي؛ يستغرق بدء تشغيل التطبيق حوالي 40 ثانية
الكود
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 بمجرد إنشاء عملية عامل. في pitchfork، توجد العملية العامل لفترة طويلة قبل أن ينتهي التطبيق من التحميل، لذلك تخرج الحلقة مبكراً ويتم تفكيك المُدير القديم بينما لا يزال الجديد قيد التشغيل.
السلوك الملاحظ
سجل الإنتاج (production.log)، إعادة تحميل واحدة (الطوابع الزمنية بتوقيت غرينتش):
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
سجل مراقب يقوم باستطلاع GET / كل ثانيتين تقريباً عبر nginx خلال تلك الفترة:
23:17:11 499 19.697s (client gave up; upstream never answered)
23:17:29 503 16.301s
23:17:31 502 1.448s
وسجل أخطاء nginx الخاص بالخطأ 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 قيمة request_time في كل سطر فاشل، لذا فإن nginx ليس هو عنق الزجاجة — فالطلبات تصل إلى عملية لا يمكنها الإجابة عليها. إعادة التعيين في الساعة 23:17:31 متسقة مع إشارة SIGTERM التي تقوم بتفكيك المُدير الصادر مع وجود اتصالات لا تزال في طابور عليه.
هذا قابل للتكرار: أنتجت إعادة تحميلين في نشر واحد فترتين متطابقتين تبلغ كل منهما حوالي 40 ثانية، وانتهت كل منهما عند سطر Booted Rails.
ليس ضغطاً على الموارد: لا توجد عمليات قتل بسبب نفاد الذاكرة (OOM) في dmesg، واستهلاك الذاكرة المؤقتة (swap) 141 ميجابايت من 2048 مستخدمة، ومتوسط الحمل 0.07، وكلا عمليتي التشغيل اكتملتا بشكل طبيعي.
الإصلاح المقترح
مطابقة فرع unicorn — الانتظار حتى يقوم المُدير الجديد بالخدمة فعلياً قبل إخراج القديم من الخدمة، وإخراجه بشكل سلس:
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
جزء استبدال TERM بـ QUIT غير غامق: يرث pitchfork دلالات إشارات unicorn، حيث تقوم QUIT بتصريف الطلبات وتعتبر TERM إيقافاً فورياً.
ملاحظة: هذا هو أول تقرير مساعده بالذكاء الاصطناعي لي. أخبروني إذا كنت أخلّ بالأدب بأي طريقة. ![]()