Libelektra: إغلاق KeySet تسرب الذاكرة

تم إنشاؤها على ٤ نوفمبر ٢٠١٩  ·  32تعليقات  ·  مصدر: ElektraInitiative/libelektra

أثناء قياس الأداء الجديد لـ elektrad ، عانيت من أعراض تسرب الذاكرة.
سيرتفع استخدام الذاكرة بعد إنشاء مقابض جديدة واستدعاء kdbGet() لكن لا ينخفض ​​أبدًا بعد ksClose() و kdbClose() .

كان شكوكي الأول هو التخزين المؤقت mmap - والذي ربما يتسبب أيضًا في حدوث تسرب للذاكرة لأن الذاكرة mmaped لا يتم تحريرها أبدًا - ولكن تعطيل التخزين المؤقت عن طريق البناء باستخدام -DPLUGINS="ALL;-cache" لم يحل مشكلتي.

خطوات إعادة إظهار المشكلة

اذهب> = 1.13

لقد أنشأت اختبارين في go-elektra repo. كلاهما ينشئ مجموعة مفاتيح اختبار مع 100000 مفتاح.

  1. ينشئ TestKeySetMemory مقابض ومجموعات مفاتيح kdbGets في حلقة وبعد الانتظار لمدة ثانية واحدة - يغلق على الفور مجموعة المفاتيح + المقبض مرة أخرى قبل البدء من جديد.
  2. ينشئ TestKeySetMemoryWithDelayedClose أيضًا مقابض ويملأ مجموعات المفاتيح بـ kdbGets - لكن يؤخر إغلاق المقبض ومجموعة المفاتيح حتى بعد تحميل جميع مجموعات المفاتيح العشرين. هذا يحصر سلوك خادم الويب elektrad.

ينتظر كلا الاختبارين 20 ثانية بعد الانتهاء للسماح للمختبِر برؤية استهلاك الذاكرة للاختبار عبر htop أو أدوات مشابهة.

الاختبار الأول ، الذي يغلق المقبض على الفور ومجموعة المفاتيح ، يحتفظ بنفس بصمة الذاكرة على طول الاختبار.

الاختبار الثاني الذي يبدأ في إغلاق المقابض ومجموعات المفاتيح فقط بعد "تحميل" كل مجموعة مفاتيح لا يحرر أي ذاكرة. حتى بعد الإجبار على جمع القمامة والانتظار لمدة 20 ثانية.

في الوقت الحالي ، لا أعرف سبب اختلاف سلوك هذه الاختبارات.

يمكنك إجراء الاختبارات عن طريق استنساخ go-elektra repo وتشغيل هذين الأمرين في الدليل الفرعي ./kdb :

PKG_CONFIG_PATH=<PATH TO elektra.pc FILE> go test  -run "^(TestKeySetMemory)\$"

PKG_CONFIG_PATH=<PATH TO elektra.pc FILE> go test  -run "^(TestKeySetMemoryWithDelayedClose)\$"

نتيجة متوقعة

يتم تحرير الذاكرة بعد kdbClose و ksClose

نتيجة فعلية

لا تتحرر الذاكرة

معلومات النظام

  • نسخة إليكترا: سيد
  • نظام التشغيل: Arch Linux
  • أحدث إصدار من go-elektra
bug urgent work in progress

ال 32 كومينتر

يبدو لي أنه من غير المحتمل أن شيئًا كهذا قد ينزلق من خلال اختبارات Valgrind و ASAN. لقد قمت بتشغيل معايير مماثلة في C ولم أتمكن من ملاحظة نفس الشيء. حتى مع mmap لا يمكنني ملاحظة أي شيء مشابه. أنا لا أستبعد ذلك بالرغم من ذلك.

هل أنت متأكد من أن الارتباطات لا تتسرب في مكان ما؟

نعم ، أنا متأكد - من الممكن فحص استخدام الذاكرة لتطبيق go أثناء وقت التشغيل ، ولا تحتوي هذه الإحصائيات على تخصيصات بواسطة رمز C وهي ليست قريبة من إجمالي استخدام الذاكرة للتطبيق / الاختبار مما يعني أن يتم تخصيص الجزء الأكبر من الذاكرة في C.

شكوكي هو أن المفاتيح لم يتم تحريرها لأن عداد المرجع هو != 0 على الرغم من أنه يجب أن يكون 0 .

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

هل يمكنك استخراج مثال C صغير يؤدي إلى حدوث التسرب؟ أفعل شيئًا مشابهًا جدًا في benchmarks/kdb.c ولا يوجد تسرب هناك ، لذلك يجب أن تفعل بالضبط ما يفعله المعيار الخاص بك لإطلاق التسرب.

شكرا على الإبلاغ القضية!

أتفق تمامًا مع mpranj هنا ، نحن بحاجة إلى تقليل المشكلة إلى الحد الأدنى ، مع وجود لغات متعددة من الصعب العثور على أي شيء.

هل يمكنك كتابة برنامج C يعيد إنتاج تسلسل المكالمات كما تم إصداره من روابط go؟ بمجرد أن نحصل على ذلك ، يمكننا تصغير برنامج C لحالة اختبار.

بالمناسبة. في محاولة لتشغيل الأمر أعلاه ، فشلت مبكرًا: # 3159

سأحاول إعادة إنتاج هذا في C حتى الغد

لقد نجحت في إعادة إنتاج هذه المشكلة هنا . تشغيل benchmarks/memoryleak.c القياسي ومشاهدة ذاكرة ترتفع حين الحصول على keysets - وليس النزول عند تحرير.

لا تنس أن تضيف كمية ملحوظة من المفاتيح في قاعدة البيانات .. لقد اختبرتها باستخدام 100 ألف مفتاح.

شكرا لخلق المثال! سألقي نظرة.

للوهلة الأولى ، هناك تسرب مباشر واحد بالضبط ، تحتاج إلى تحرير مفتاح الوالدين:
https://github.com/raphi011/libelektra/blob/afcbcf5c8138be8de6ba9b1a9e559bc673ff887f/benchmarks/memoryleak.c#L22

ربما لا يكون هذا التسرب الصغير هو سبب ملاحظاتك.

كملاحظة جانبية: باستخدام ذاكرة التخزين المؤقت mmap ، ألاحظ حتى استهلاك أقل للذاكرة من دون ذاكرة التخزين المؤقت. ربما أحتاج إلى إضافة هذا إلى رسالتي: ابتسم :. (ذاكرة التخزين المؤقت: 200 ميجا تقريبًا مقابل عدم وجود ذاكرة تخزين مؤقت: 600 ميجا تقريبًا)

يبدو أننا "نسرب" الذاكرة عبر dlopen() / dlclose() . يمكن ملاحظة ما يلي باستخدام kdb ولكن ليس باستخدام kdb-static (مجرد مقتطف):

valgrind --leak-check=full --show-leak-kinds=all ./bin/kdb ls user
[...]
==48451== 1,192 bytes in 1 blocks are still reachable in loss record 6 of 6
==48451==    at 0x483AB1A: calloc (vg_replace_malloc.c:762)
==48451==    by 0x400BB11: _dl_new_object (in /usr/lib64/ld-2.30.so)
==48451==    by 0x400642F: _dl_map_object_from_fd (in /usr/lib64/ld-2.30.so)
==48451==    by 0x4009315: _dl_map_object (in /usr/lib64/ld-2.30.so)
==48451==    by 0x400DB24: openaux (in /usr/lib64/ld-2.30.so)
==48451==    by 0x4DFE8C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48451==    by 0x400DF6A: _dl_map_object_deps (in /usr/lib64/ld-2.30.so)
==48451==    by 0x4013AC3: dl_open_worker (in /usr/lib64/ld-2.30.so)
==48451==    by 0x4DFE8C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48451==    by 0x401363D: _dl_open (in /usr/lib64/ld-2.30.so)
==48451==    by 0x496139B: dlopen_doit (in /usr/lib64/libdl-2.30.so)
==48451==    by 0x4DFE8C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
[...]

يفتح المثال الخاص بك العديد من المقابض ، لذا يبدو كالتالي:

==48735== 72,704 bytes in 1 blocks are still reachable in loss record 8 of 8
==48735==    at 0x483880B: malloc (vg_replace_malloc.c:309)
==48735==    by 0x4F860A9: ??? (in /usr/lib64/libstdc++.so.6.0.27)
==48735==    by 0x400FD59: call_init.part.0 (in /usr/lib64/ld-2.30.so)
==48735==    by 0x400FE60: _dl_init (in /usr/lib64/ld-2.30.so)
==48735==    by 0x4013DBD: dl_open_worker (in /usr/lib64/ld-2.30.so)
==48735==    by 0x4A088C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48735==    by 0x401363D: _dl_open (in /usr/lib64/ld-2.30.so)
==48735==    by 0x48C739B: dlopen_doit (in /usr/lib64/libdl-2.30.so)
==48735==    by 0x4A088C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48735==    by 0x4A08962: _dl_catch_error (in /usr/lib64/libc-2.30.so)
==48735==    by 0x48C7B08: _dlerror_run (in /usr/lib64/libdl-2.30.so)
==48735==    by 0x48C7429: dlopen@@GLIBC_2.2.5 (in /usr/lib64/libdl-2.30.so)

في المثال ، تقوم بفتح الكثير من مقابض KDB ، مما يؤدي إلى إضافة هذا التأثير إلى استهلاك كبير للذاكرة. إذا فتحت مقبض واحد فقط ، فيجب أن يكون أقل. @ raphi011 لا أعرف ما إذا كان يمكنك استخدام kdb-static لمقاييس الأداء الخاصة بك. عند ربط المعايير بمكتبة elektra-static بإمكاني ملاحظة نفس الشيء بعد الآن:

==54836== HEAP SUMMARY:
==54836==     in use at exit: 0 bytes in 0 blocks
==54836==   total heap usage: 6,456,631 allocs, 6,456,631 frees, 272,753,180 bytes allocated
==54836== 
==54836== All heap blocks were freed -- no leaks are possible
==54836== 
==54836== For lists of detected and suppressed errors, rerun with: -s
==54836== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

لست متأكدًا حاليًا مما يمكننا فعله حيال مشاكل dlopen() .

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

ما زلت أعتقد أن هذا ليس أصل المشكلة. المشكلة هي أن Keys / KeySets لم يتم تحريرها. إذا قمت بتغيير المفتاح الأصلي إلى "system" والذي يحتوي على مفاتيح أقل بكثير ، فسترى أن استهلاك الذاكرة أقل بكثير.

@ markus2330 ما رأيك؟

تحرير: ننسى هذا.

لا أعتقد أننا نسرب الذاكرة هناك.
https://gist.github.com/mpranj/bbdf00af308ed3f5b3f0f35bc832756f~~

يمكنني ملاحظة نفس الشيء الذي تصفه مع HTOP واستخدام الذاكرة باستخدام الكود من الجوهر أعلاه.

إذن أنت تخبرني أنه من الطبيعي تمامًا ألا يتقلص استهلاك ذاكرة elektra أبدًا؟

إذن أنت تخبرني أنه من الطبيعي تمامًا ألا يتقلص استهلاك ذاكرة elektra أبدًا؟

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

https://stackoverflow.com/questions/40917024/memory-leak-after-using-calloc-and-free

يبدو أنه بسبب قيامك بتغيير المؤشر قبل تحريره

يبدو أنه بسبب قيامك بتغيير المؤشر قبل تحريره

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

ما هو أكثر صلة: لقد لاحظت ما تقوم بالإبلاغ عنه ببساطة باستخدام benchmark_createkeys الذي لا يستخدم KDB على الإطلاق. هناك أيضًا الموارد ليست مجانية () د على الفور ، ولكن تظهر valgrind 0 تسربًا على الإطلاق. أنا محتار.

@ markus2330 ما رأيك؟

مع KeySets وحدها لن يكون هناك أي تسرب على الإطلاق. (مع KDB ، لا يمكننا التحكم في كل شيء بشكل كامل لأن المكونات الإضافية قد تقوم بتحميل مكتبات أخرى تتسرب.)

@ raphi011 هل يمكنك إنشاء توجيهي إلى مثال بسيط لإعادة إنتاج المشكلة؟

https://github.com/raphi011/libelektra/tree/memoryleak هنا تذهب. لقد وسعت المثال السابق عن طريق طباعة عدد المفاتيح التي لم يتم تحريرها بواسطة ksDel بسبب المراجع الرئيسية > 0 .

إذا قمت بتشغيل المعيار بدون valgrind ، يمكنك أن ترى أن المفاتيح لم يتم تحريرها.

لا يمكنني تجميع هذا الريبو ، أتلقى الخطأ:

CMake Error: Error processing file: /home/markus/Projekte/Elektra/repos/libelektra/scripts/cmake/ElektraManpage.cmake
make[2]: *** [src/bindings/intercept/env/CMakeFiles/man-kdb-elektrify-getenv.dir/build.make:61: ../doc/man/man1/kdb-elektrify-getenv.1] Fehler 1
make[1]: *** [CMakeFiles/Makefile2:17236: src/bindings/intercept/env/CMakeFiles/man-kdb-elektrify-getenv.dir/all] Fehler 2

هل يمكنك تغيير العنوان إلى السيد الرئيسي من فضلك؟

ويرجى كتابة العلاقات العامة ، فمن الأسهل بكثير رؤية التغييرات ، إذن.

إذا قمت بتشغيل المعيار بدون valgrind ، يمكنك أن ترى أن المفاتيح لم يتم تحريرها.

هل يمكنك نسخ إخراج التشغيل هنا؟

لقد لاحظت ما تقوم بالإبلاغ عنه ببساطة عن طريق استخدام benchmark_createkeys الذي لا يستخدم KDB على الإطلاق. هناك أيضًا الموارد ليست مجانية () د على الفور ، ولكن تظهر valgrind 0 تسربًا على الإطلاق. أنا محتار.

أعتقد أننا يجب أن نتبع هذا التتبع أولاً ، لأنه من الأسهل بكثير فهم ما إذا كان لدينا مجموعات مفاتيح فقط.

ربما أضفنا قمعًا خاطئًا إلى valgrind؟

كملاحظة جانبية: باستخدام ذاكرة التخزين المؤقت mmap ، ألاحظ حتى استهلاك أقل للذاكرة من دون ذاكرة التخزين المؤقت. ربما أحتاج إلى إضافة هذا إلى ابتسامة أطروحتي. (ذاكرة التخزين المؤقت: 200 ميجا تقريبًا مقابل عدم وجود ذاكرة تخزين مؤقت: 600 ميجا تقريبًا)

هذه بالتأكيد أخبار رائعة.

ربما أضفنا قمعًا خاطئًا إلى valgrind؟

أبلغت Valgrind عن عدم استخدام أي قمع عندما أجريت الاختبارات ، لكن الأميال التي قطعتها تختلف.

أعتقد أنني وجدت المشكلة خلال معاييري. بالنسبة لي ، تكمن المشكلة في التخصيص الحريص لمجموعة المفاتيح الوصفية لكل مفتاح. بعد إجراء اختبار قذر على فرعي ، إزالة تخصيص Meta KeySet الشغوف ، ينخفض ​​استهلاك الذاكرة بسرعة بعد ksDel (). ربما تم إصلاح هذا بواسطة # 3142 لأنه يغير الكود الذي أتحدث عنه.

حسنًا ، دعنا نرى ما إذا كان # 3142 يحل المشكلة!

@ raphi011 هل

تم دمج 3142 الآن. @ raphi011 هل يمكنك التحقق مما إذا كانت المشكلة لا تزال قائمة؟

للأسف نعم

بالنسبة لي يبدو أنه على الأقل يحل / يخفف من بعض المشاكل. يمكنني الآن تشغيل الاختبار بملايين المفاتيح باستخدام ذاكرة تبلغ 2 غيغابايت تقريبًا ، بينما تعطلت من قبل بسبب استخدام ذاكرة 20 غيغابايت لنفس المعيار.

بالنسبة لي يبدو أنه على الأقل يحل / يخفف من بعض المشاكل. يمكنني الآن تشغيل الاختبار بملايين المفاتيح باستخدام ذاكرة تبلغ 2 غيغابايت تقريبًا ، بينما تعطلت من قبل بسبب استخدام ذاكرة 20 غيغابايت لنفس المعيار.

لم أقارنه بالإصدار السابق حتى الآن ، لكنني متأكد تمامًا من أنه يستخدم ذاكرة أقل من ذي قبل. سوف تحقق اليوم!

لم يتم تحرير الذاكرة رغم ذلك.

تحتاج إلى الاتصال بـ malloc_trim(0) بعد كل free (اقرأ: ksDel ). هذا يفرض على glibc إعادة الذاكرة على الفور إلى نظام التشغيل. يجب أن يصلح هذا "السلوك الغريب" الذي تراه يا رفاق. أوه واستمتع بالقراءة والبحث في glibc :-)

لقد أنشأت # 3183 لمزيد من الاختبارات / الإصلاح.

لقد وجدت memleak واحد (في لم يتم تحرير مفاتيح العودة kdbGet).

لكن العدد المتزايد من "ksClose لم يحرر 532 مفتاحًا" ربما يكون فقط التحذيرات التي تم جمعها في المفتاح الرئيسي. إذا جعلت NUM_RUNS أعلى ، على سبيل المثال 100 ، فإنها ستظل في حالة ركود في مرحلة ما ، لأن عدد التحذيرات يقتصر على 100. بالنسبة لي ، فإنه يصل إلى الحد الأقصى. "لم يحرر ksClose 901 مفتاحًا". سيؤدي وجود مفتاح رئيسي لكل مقبض إلى حل هذه المشكلة.

مغلق بواسطة # 3183

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

markus2330 picture markus2330  ·  3تعليقات

markus2330 picture markus2330  ·  4تعليقات

markus2330 picture markus2330  ·  4تعليقات

mpranj picture mpranj  ·  3تعليقات

dominicjaeger picture dominicjaeger  ·  3تعليقات