Laravel 13.40: مراقبة الوظائف البطيئة في Queue وSchedule وإصلاح أمني

صدر Laravel 13.40 في 3 نوفمبر 2026 ومعه دالتان لرصد الوظائف والمهام المجدولة التي تتجاوز مدة محددة، وإصلاح أمني في Concurrency. إليك التفاصيل وكيف تترقّى.

إذا كان لديك job يتباطأ تدريجيًا أو مهمة مجدولة تتمدّد من دقيقتين إلى ساعة دون أن ينتبه أحد، فهذا الإصدار يمنحك أخيرًا خطّاف مراقبة جاهزًا في الإطار نفسه بدل كتابة حلول مخصصة. وفيه أيضًا إصلاح أمني يستحق أن تقرأه قبل أن تؤجّل التحديث.

ما الذي جاء في Laravel 13.40؟

بحسب صفحة الإصدار على GitHub، يضم الإصدار نحو 40 طلب دمج (Pull Request). أبرزها:

  • Queue::whenJobRunsLongerThan لرصد الـ jobs البطيئة (PR رقم 62141).
  • Schedule::whenTaskRunsLongerThan لرصد المهام المجدولة البطيئة (PR رقم 62233).
  • إصلاح أمني يخص Concurrency (PR رقم 62297).
  • دالتا $middleware->insertBefore() و$middleware->insertAfter() لإدراج middleware قبل أو بعد middleware معيّن، وتعديل $middleware->replace() ليقبل مصفوفة من البدائل.
  • إعادة الاتصال بقاعدة البيانات بعد فقدانه في منتصف transaction، والتراجع إلى savepoint عند أخطاء التزامن في transactions المتداخلة.
  • إصلاحات متفرقة لـ SQL Server وPostgreSQL وMySQL وMariaDB.
  • إصلاح حالة Redis تتعلق بمسح الكاش المرتبط بوسوم كثيرة (tagged cache) كان قد يحجب Redis أثناء المسح.
  • إظهار الـ jobs المُعادة إلى الطابور بحالة released في مخرجات queue:work، وإطلاق حدث ScheduledTaskSkipped عندما تكون المهمة قد نُفِّذت على سيرفر آخر.

سنركّز على الثلاثة الأولى لأنها الأكثر تأثيرًا عمليًا.

مراقبة الـ jobs البطيئة في Laravel Queue

أضاف Jack Bayliss الدالة Queue::whenJobRunsLongerThan، وقد دمجها Taylor Otwell في 28 أكتوبر 2026. تسدّ هذه الدالة فجوة بجوار خطّافات موجودة أصلًا مثل whenQueryingForLongerThan للاستعلامات وwhenRequestLifecycleIsLongerThan للطلبات. المثال في وصف الـ PR يبدو هكذا:

use Carbon\CarbonInterval;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Queue;

Queue::whenJobRunsLongerThan(CarbonInterval::seconds(90), function ($job, $duration) {
    Log::warning("{$job->resolveName()} استغرق وقتًا أطول من المتوقع");
});

تُسجَّل الدالة في العادة داخل boot() في AppServiceProvider. الـ callback يستقبل كائن الـ job والمدة. صفحة الـ PR لا توثّق وحدة قياس $duration رسميًا، لذا اقرأ التوثيق الرسمي أو شيفرة الإطار قبل الاعتماد على الرقم في تنبيهات حرجة.

لماذا هذا مفيد؟

الـ job الذي يعمل أبطأ من المعتاد لا يفشل، وبالتالي لا يظهر في failed_jobs. هذا النوع من التدهور الصامت هو ما يستهلك workers ويؤخّر باقي الطابور. بهذا الخطّاف يمكنك إرسال تحذير إلى نظام السجلات أو أداة المراقبة عند تجاوز عتبة تحددها لكل التطبيق، دون تعديل كل job على حدة.

مراقبة المهام المجدولة: Schedule::whenTaskRunsLongerThan

بعد أيام دُمجت في 30 أكتوبر 2026 الدالة المقابلة للجدولة، وكاتبها نفسه. يقول وصف الـ PR إنه لم تكن هناك مقاييس للمهام المجدولة، وإن الدالة تساعد في اكتشاف التراجعات. تأخذ الدالة عتبة زمنية (المثال يستخدم CarbonInterval بمدة 45 دقيقة) ثم callback يستقبل المهمة Event $task والزمن float $runtime:

use Carbon\CarbonInterval;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;

Schedule::whenTaskRunsLongerThan(CarbonInterval::minutes(45), function ($task, $runtime) {
    Log::warning("المهمة {$task->getSummaryForDisplay()} استغرقت {$runtime} ثانية");
});

المثال في الـ PR يتعامل مع $runtime على أنه ثوانٍ. ولا تذكر صفحة الـ PR متى يجري الفحص بالضبط (أثناء التنفيذ أم بعده)، فلا تفترض ذلك في تصميمك للتنبيهات.

مع الحدث الجديد ScheduledTaskSkipped (يُطلق بحسب عنوان الـ PR عندما تكون المهمة قد نُفِّذت أصلًا على سيرفر آخر) صار لديك رؤية أفضل لما يجري في الـ scheduler دون أدوات خارجية.

كيف تصمّم تنبيهات عملية بهذين الخطّافين؟

الخطّاف أداة رصد لا بديل لإعدادات الإيقاف. بحسب وصف الـ PR، وظيفته تشغيل callback عند تجاوز العتبة، فلا تعتمد عليه بدل خاصية timeout الخاصة بالـ job. بعض الأفكار للاستفادة منه:

  • اختر عتبة قريبة من الزمن الطبيعي للـ jobs الثقيلة لا الخفيفة، وإلا امتلأ السجل بتحذيرات لا قيمة لها.
  • سجّل اسم الـ job والمدة وربما معرّف الطابور، لتفرّق بين job بطيء بطبيعته وآخر تدهور أداؤه.
  • أرسل التحذير إلى قناة تراقبها فعلًا (Slack أو Sentry أو نظام سجلات مركزي) بدل الاكتفاء بملف laravel.log.
  • راجع الأنماط أسبوعيًا: job يتكرر تجاوزه للعتبة غالبًا ينتظر فهرسًا ناقصًا أو استعلامًا ثقيلًا أو خدمة خارجية بطيئة.

الإصلاح الأمني في Concurrency

الأهم أمنيًا هو الـ PR رقم 62297. وفق وصفه، كان ProcessDriver في الإطار يعامل مخرجات العملية الابنة (stdout) على أنها بروتوكول إرجاع النتيجة، ثم يفكّ تسلسل (unserialize) حقل result في العملية الأم. وبما أن مخرجات المهمة نفسها تشارك القناة ذاتها، فإن مهمة تطبع نصًا يتحكم فيه المستخدم قد تزوّر "غلاف النتيجة"، فيُفكّ تسلسل محتوى خبيث في عملية الويب، وهو ما يفتح الباب نظريًا لتنفيذ شيفرة عن بُعد عبر gadget chain.

الحل كما يصفه الـ PR: تُغلَّف النتيجة الآن بسطر محدِّد (sentinel) مع توقيع HMAC محسوب بمفتاح التطبيق، ولا تقبل العملية الأم إلا إطارًا يتحقق توقيعه. يتغير شكل البيانات المتبادلة، لكن الـ PR يقول إن ذلك آمن لأن نفس إصدار الإطار يكتب الغلاف ويقرؤه، وإن السلوك الموثَّق لم يتغير.

ملاحظات مهمة:

  • تفاصيل الأثر وحدود الثغرة مأخوذة من وصف كاتب الـ PR ولم أتحقق منها بشكل مستقل، ولم أجد إعلانًا أمنيًا (advisory) منفصلًا عند كتابة المقال.
  • الحماية تعتمد على بقاء APP_KEY سرّيًا.
  • إن كنت تستخدم Concurrency::run ومهامك تطبع مخرجات مصدرها مستخدمون، فهذا سبب إضافي لتحديث الإصدار.

كيف تترقّى؟

الترقية ضمن سلسلة 13.x لا تتطلب تغييرات في الشيفرة عادة:

composer update laravel/framework
php artisan test

بعد التحديث جرّب الخطّافين الجديدين في بيئة staging بعتبات واقعية، وراقب ضجيج التنبيهات قبل تفعيلها في الإنتاج. وإن كنت لا تزال على Laravel 12، فقد صدر الإصدار 12.70.2 في 27 أكتوبر 2026 بإصلاحات منقولة من سلسلة 13.x، لكن راجع جدول الدعم الرسمي لمعرفة المدة المتبقية لدعم إصدارك.

الخلاصة

Laravel 13.40 ليس إصدارًا ضخمًا، لكنه يعالج مشكلتين واقعيتين: الوظائف والمهام التي تبطؤ بصمت، وثغرة محتملة في Concurrency. الخطوة العملية اليوم: حدّث، وسجّل Queue::whenJobRunsLongerThan وSchedule::whenTaskRunsLongerThan بعتبة معقولة لأبطأ jobs لديك، واربط التحذيرات بقناة تنبيه فعلية.

المصادر