حول تسرب الذاكرة

webman هو إطار ثابت الذاكرة، لذا نحتاج إلى الاهتمام قليلاً بحالات تسرب الذاكرة. ومع ذلك، لا يجب على المطورين القلق كثيرًا، لأن تسرب الذاكرة يحدث في ظروف شديدة للغاية، ومن السهل تفاديه. تجربة تطوير webman تقريبًا نفس تجربة تطوير الإطار التقليدي، ولا حاجة لإجراءات زائدة لإدارة الذاكرة.

ملحوظة
يتم رصد استخدام الذاكرة لجميع العمليات بواسطة عملية المراقبة المضمنة في webman. إذا كان استخدام الذاكرة للعملية قد تقترب من القيمة المحددة في php.ini لـ memory_limit، فسيتم إعادة تشغيل العملية بشكل آمن تلقائيًا، مما يتيح تحرير الذاكرة، دون تأثير على العملية العادية.

تعريف تسرب الذاكرة

مع زيادة الطلبات، يزداد استخدام الذاكرة من قبل webman بصورة غير محدودة (يرجى ملاحظة أنها غير محدودة). هذا هو نوع من تسرب الذاكرة. إذا زادت الذاكرة، ولكنها لم تزد بعد ذلك، فهذا ليس تسرب ذاكرة.

عمومًا، استخدام الذاكرة قدره بضعة عشرات من الميغابايت هو حالة طبيعية، عندما يتعامل العمل بطلبات كبيرة للغاية أو يدير كميات كبيرة من الاتصالات، فقد يصل استخدام الذاكرة إلى مئات الميغابايت، وهو أمر شائع. قد لا يعيد PHP جميع الذاكرة للنظام بعد استخدامها، بل يتركها للاستخدام مرة أخرى، لذا قد يحدث زيادة في استخدام الذاكرة بعد معالجة طلب كبير دون إعادة الذاكرة، وهذا هو ظاهرة طبيعية. (يمكن استخدام طرق ضغط ذاكرة الـ gc_mem_caches() لإعادة الذاكرة الفارغة جزئيًا)

كيف يحدث تسرب الذاكرة

يجب توفر شرطين لحدوث تسرب الذاكرة:

  1. وجود مصفوفة طويلة العمر (يرجى ملاحظة أنها مصفوفة طويلة العمر، وليس المصفوفات العادية)
  2. وهذه المصفوفة طويلة العمر ستتوسع بشكل غير محدود (الأعمال تقوم بإدراج البيانات فيها بشكل لا نهائي، دون تنظيف البيانات)

إذا تم استيفاء الشرطين 1 و 2 معًا (يرجى ملاحظة أنه إذا تم تحقيق الشرطين معًا)، فسيحدث تسرب الذاكرة. وإذا لم يتم استيفاء الشروط المذكورة أعلاه أو تم تحقيق شرط واحد فقط، فلن يحدث تسرب للذاكرة.

مصفوفة طويلة العمر

تشمل مصفوفة طويلة العمر في webman:

  1. مصفوفة تحتوي على الكلمة المفتاحية static
  2. خاصية الجدول المفرد للفئة
  3. مصفوفة تحتوي على الكلمة المفتاحية global

يرجى ملاحظة
يُسمح في webman باستخدام البيانات طويلة العمر، ولكن يجب ضمان أن البيانات داخل الجداول محدودة، وأن عدد العناصر لن يتوسع بشكل لا نهائي.

وفيما يلي أمثلة لتوضيح ذلك

مصفوفة static متوسعة بشكل لا نهائي

class Foo
{
    public static $data = [];
    public function index(Request $request)
    {
        self::$data[] = time();
        return response('hello');
    }
}

المصفوفة $data المُعرفة بكلمة المفتاحية static هي مصفوفة طويلة العمر، وفي المثال المعطى، تزداد المصفوفة $data بصورة غير محدودة مع استمرار الطلب، مما يؤدي إلى تسرب الذاكرة.

خاصية الجدول المفرد الذي يتوسع بشكل لا نهائي

class Cache
{
    protected static $instance;
    public $data = [];

    public function instance()
    {
        if (!self::$instance) {
            self::$instance = new self;
        }
        return self::$instance;
    }

    public function set($key, $value)
    {
        $this->data[$key] = $value;
    }
}

كود الاستدعاء

class Foo
{
    public function index(Request $request)
    {
        Cache::instance()->set(time(), time());
        return response('hello');
    }
}

تُعيد Cache::instance() كائنًا من الفئة Cache، وهو كائن طويل العمر. على الرغم من أن خاصية $data لم تستخدم كلمة المفتاحية static، إلا أنه نظرًا لأن الفئة نفسها طويلة العمر، فإن $data أيضًا مصفوفة طويلة العمر. مع استمرار إضافة بيانات مفاتيح مختلفة إلى مصفوفة $data، يزداد استخدام الذاكرة بشكل متزايد، مما يؤدي إلى تسرب الذاكرة.

يرجى ملاحظة
إذا كان بإمكان Cache::instance()->set(key, value) إضافة عدد محدود من المفاتيح، فلن يحدث تسرب ذاكرة، لأن مصفوفة $data لن تتوسع بشكل غير محدود.

مصفوفة global المتوسعة بشكل لا نهائي

class Index
{
    public function index(Request $request)
    {
        global $data;
        $data[] = time();
        return response($foo->sayHello());
    }
}

ثمة شيء أيضًا يمكن أن يتم استدعاؤه في الدالة أو الميثود يسمح بـ global وهو أيضًا مصفوفة طويلة العمر، ففي الشفرة المعطاة عند ارتفاع عدد الطلبات، ستحدث تسربًا في الذاكرة. وبالمثل في دالة أو ميثود يملك أخصائية static ايضا سيكون هو مصفوفة طويلة العمر، إذا كان الدالة او الميثود يضيف نوعية العنصر الى المصفوفة وهي غير محدودة، سيكون لدينا تسرب في الذاكرة، كما في الشفرة المعطاة:

class Index
{
    public function index(Request $request)
    {
        static $data = [];
        $data[] = time();
        return response($foo->sayHello());
    }
}

الاقتراحات

يُفضل عدم الاهتمام بتسرب الذاكرة كثيرًا، لأنه نادرًا ما يحدث، وإذا حدث لسوء الحظ يمكننا العثور على الشفرة المسببة للتسرب عن طريق اختبار الضغط، وبذلك يمكننا تحديد المشكلة. حتى إذا لم يجد المطورون نقطة تسرب، فإن خدمة المراقبة المضمنة في webman ستقوم بإعادة تشغيل العمليات التي تسبب تسرب الذاكرة في الوقت المناسب وبأمان، مما يؤدي إلى تحرير الذاكرة.

إذا كنت ترغب حقًا في تفادي تسرب الذاكرة بقدر الإمكان، فيُرجى النظر في الاقتراحات التالية.

  1. تجنب استخدام كلمة المفتاحية global و static للمصفوفات، وإذا استخدمتها فتأكد من عدم توسع هذه المصفوفات بشكل لا نهائي
  2. بخصوص الفئات التي لا تعرفها جيدًا، فضلًا تجنب استخدام الكائن الوحيد، واستخدام الكلمة الرئيسية new للتهيئة. إذا كنت بحاجة إلى كائن وحيد، يتوجب فحص ما إذا كانت تحتوي على خصائص مصفوفة تتوسع بشكل لا نهائي.