خادم Ubuntu 22.04، PHP 8.3 FPM، عامل يستدعي ffmpeg عبر proc_open لتحويل مقاطع قصيرة (10–40 ميغابايت). بعد أسبوع من النشر بدأ عدد العمليات يتضاعف: ps -eo pid,ppid,etime,cmd | grep ffmpeg كان يعطي 60–80 عملية، أعمارها بين دقائق وساعات، وبعضها ppid يساوي 1. الذاكرة لم تنفجر لكن التحويلات صارت تتباطأ لأن كل ffmpeg ينافس على المعالج.
النسخة الأولى من الكود كانت هكذا:
$p = proc_open($cmd, $descriptors, $pipes);
fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$pid = proc_get_status($p)['pid'];
// ... نقرأ المخرجات ونحدّث الحالة
if ($timeout) { return; } // نترك العملية تعمل في الخلفية
الخطأ لم يكن في proc_open نفسه، بل في الاعتماد على proc_get_status بعد انتهاء العملية. في PHP 8.3 صار proc_get_status يعيد running = false بعد أول استدعاء يلاحظ فيه الخروج، والاستدعاء الثاني يعيد exitcode = -1. كنا نستخدم الحالة لتحديث صف في قاعدة البيانات، فكانت بعض الصفوف تبقى running إلى الأبد، والعامل يعيد المحاولة، فيولد ffmpeg جديد فوق القديم.
ما فعلته: أولًا فصلت دورة الحياة عن قراءة الحالة. صرت أسجّل PID فور الإنشاء، وأستخدم posix_kill($pid, 0) للتحقق من الحياة بدل الاعتماد على proc_get_status بعد الخروج:
function alive(int $pid): bool {
if ($pid <= 0) return false;
return @posix_kill($pid, 0);
}
ثانيًا، لكل عملية مجموعة عمليات خاصة بها بدل تركها في مجموعة FPM:
$cmd = 'setsid ' . escapeshellcmd($bin) . ' ' . $args . ' > /tmp/out.log 2>&1';
$p = proc_open($cmd, [], $pipes);
$status = proc_get_status($p);
$pgid = $status['pid']; // setsid يجعل PID هو PGID
وعند المهلة أو عند إلغاء المهمة: posix_kill(-$pgid, SIGTERM) ثم SIGKILL بعد 5 ثوان. هذا يقتل ffmpeg وأي عملية فرعية أنشأها، وهو ما لم يكن proc_terminate($p) يفعله لأنه يقتل الأب فقط.
ثالثًا، أضفت فحصًا دوريًا في cron كل دقيقة يقتل أي عملية يتيمة عمرها أكثر من المهلة المعلنة، ويعيد صفها إلى failed. السبب أن أي إعادة تشغيل لـ FPM أو OOM تقطع العلاقة بالأب، فتبقى العملية بلا مراقب.
الثمن واضح: صرت أحتاج صلاحية قتل عمليات من مستخدم PHP، وأي خطأ في حساب PGID قد يقتل مجموعة خاطئة. لذلك أتحقق أن PID ينتمي إلى مسار ffmpeg المتوقع قبل الإرسال. كذلك setsid غير متاح على كل الأنظمة، وعلى Windows هذا الحل لا يعمل أصلًا.
الحد الذي ما زلت أصطدم به: إذا كان ffmpeg نفسه ينتج ملفًا كبيرًا على قرص مشترك، فقتل العملية لا يحرر المساحة المؤقتة تلقائيًا. وما زلت غير متأكد من سلوك posix_kill($pid, 0) عندما يعاد استخدام PID بسرعة على نظام مزدحم؛ لهذا أضفت تحققًا من وقت بدء العملية عبر /proc/$pid/stat قبل القتل.
سؤالي: كيف تتعاملون مع PID reuse عند مراقبة عمليات spawned من PHP على خوادم عالية الحمل؟ هل تعتمدون على cgroups، أم على pidfd، أم تكتفون بفحص وقت البدء؟


0 条评论
还没有评论,写下第一条。