واحد آخر من Heisenbug تجاوز التمساح

واحد آخر من Heisenbug تجاوز التمساح

$> set -o pipefail

$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!

$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли

ومن fortune برنامج مشروط بدون exit(rand()).

هل يمكن ان توضح؟ ما هو الخطأ هنا?

الاستطراد الغنائي التاريخي

لقد تعرفت على Heisenbug لأول مرة منذ ربع قرن. ثم بالنسبة للبوابة في FaxNET كان من الضروري إنشاء العديد من المرافق عبر أنابيب "لعب لعبة الداما" ضمن FreeBSD. كما هو متوقع، اعتبرت نفسي مبرمجًا متقدمًا وذو خبرة كبيرة. ولذلك، كنت أنوي أن أفعل كل شيء بعناية وحرص قدر الإمكان، مع إيلاء اهتمام خاص لمعالجة الأخطاء...

تجربتي السابقة في التعامل مع الأخطاء في sendmail وuucp/uupc أضافت إلى اجتهادي في "التعامل الشامل مع الأخطاء". ليس هناك فائدة من الغوص في تفاصيل تلك القصة، لكنني كافحت مع Heisenbug لمدة أسبوعين لمدة 10-14 ساعة. لذلك تذكرت، وبالأمس توقف هذا التعارف القديم للزيارة مرة أخرى.

TL ؛ إجابة الدكتور

فائدة head علبة اغلاق القناة من fortune فورا بمجرد أن يقرأ السطر الأول. لو fortune إخراج أكثر من سطر واحد، ثم المكالمة المقابلة write() سيُرجع خطأً أو تقريرًا يفيد بأن عدد وحدات البايت الناتجة أقل من المطلوب. في المقابل، مكتوبة مع معالجة الأخطاء بعناية fortune وله الحق في أن يعكس هذا الوضع في حالة خروجه. ثم بسبب التثبيت set -o pipefail سيعمل || echo "Вы проиграли".

ومع ذلك، head قد لا تفعل ذلك في الوقت المناسب إغلاق من قبل fortune سوف تنتهي من إخراج البيانات. ثم سوف تعمل && echo "Повезло!".

في أحد أعمالي اليوم GNUMakefile هناك واحد شظية:

echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'

ترجمت إلى إنسان

إنه أمر شائع هنا ل صنع غنو и سحق بطريقة المترجم باستخدام الخيار --version فإنه يسأل من هو، وإذا كان الخيار غير معتمد، ثم يتم إدراج كعب "الرجاء استخدام المترجم المتوافق مع دول مجلس التعاون الخليجي أو CLANG".

مثل لوحة مرجعية يمكن العثور عليها في أي مكان. لقد ظهر في هذا المكان منذ وقت طويل وعمل بشكل مثالي في كل مكان (Linux، Solaris، OSX، FreeBSD، وسل إلخ.). ولكن بالأمس في التلينكس على المنصة البروس 2000 (E2K) لقد لاحظت:

#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"

بصراحة، لم أتعرف على الفور على "معارفي" القدامى. علاوة على ذلك، فقد تم بالفعل اختبار المشروع عدة مرات على Elbrus وفي ظل الكثير من التوزيعات المختلفة، بما في ذلك Alt. مع العديد من المترجمين، إصدارات GNU Make و bash. ولذلك، لم أكن أريد أن أرى خطأي هنا.

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

echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"

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

وبطبيعة الحال، strace لدينا كل شيء! وبعد أن كتبت خطبة طويلة، ولكن لم يكن لدي الوقت للضغط على Enter، تعرفت على صديقي القديم السيد Heisenbug والمطورين مترجم نفسي قبل 25 سنة حنين للماضي… وقررت أن أحزن وأكتب هذه الملاحظة 😉

بالمناسبة، مثل أي احترام الذات هايسنبوغ، تحت strace يفضل عدم التكاثر.

ماذا يحصل؟

  • فائدة head يحق له (أو بالأحرى أنه مجبر) على إغلاق القناة التي تتم قراءتها بمجرد قراءة العدد المطلوب من الأسطر.
  • كاتب برنامج توليد البيانات (في هذه الحالة cc) علبة طباعة خطوط متعددة و حر القيام بذلك من خلال مكالمات متعددة write().
  • إذا سيكون لدى القارئ وقت لإغلاق القناة من جانبه قبل انتهاء التسجيل من جانب الكاتب، ثم سيتلقى الكاتب خطأ.
  • برنامج الكاتب يحق ل كلاهما يتجاهل خطأ كتابة القناة ويعكسه في رمز الإكمال الخاص بك.
  • بسبب التثبيت set -o pipefail سيكون رمز إكمال المسار غير صفري (خاطئ) إذا كانت النتيجة غير صفرية من عنصر واحد على الأقل، وبعد ذلك سيعمل || echo "Please use GCC or CLANG compatible compiler".

قد تكون هناك اختلافات اعتمادًا على كيفية عمل برنامج الكاتب مع الإشارات. على سبيل المثال، قد يتم إنهاء البرنامج بشكل غير طبيعي (مع الإنشاء التلقائي لحالة إنهاء غير صفرية/خطأ)، أو write() سيعود نتيجة كتابة بايتات أقل مما هو مطلوب وتعيين errno = EPIPE.

على من يقع اللوم؟

في الحالة الموصوفة قليلا من كل شيء. خطأ في التعامل في cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) ليس كذلك متكرر. وفي كثير من الحالات، يكون من الأفضل توخي الحذر، على الرغم من أن هذا يكشف عن عيوب مفاجئة في النموذج النموذجي المصمم للسلوك التقليدي.

ماذا تفعل؟

خطأ:

fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"

تصحيح:

  1. (fortune && echo "Успешно" || echo "Ошибка") | head -1

    هنا، سيتم التعامل مع الإغلاق المبكر للأنبوب بواسطة مترجم الأوامر عند خدمة خط الأنابيب المتداخل ("داخل" الأقواس). وبناء على ذلك، إذا fortune سيتم الإبلاغ عن خطأ في الكتابة إلى قناة مغلقة في الحالة، ثم الإخراج || echo "Ошибка" لن تصل إلى أي مكان، لأن القناة مغلقة بالفعل.

  2. fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"

    ها هي الأداة cat بمثابة المثبط لأنه يتجاهل الخطأ EPIPE عند الانسحاب. وهذا يكفي للاستنتاج الآن fortune صغير (عدة أسطر) ويناسب المخزن المؤقت للقناة (من 512 بايت إلى ≈64K، في معظم أنظمة التشغيل ≥4K). وإلا قد تعود المشكلة.

كيفية المعالجة بشكل صحيح EPIPE وأخطاء التسجيل الأخرى؟

لا يوجد حل واحد صحيح، ولكن هناك توصيات بسيطة:

  • EPIPE مطلوب يجب معالجتها (وينعكس ذلك في حالة الخروج) عند إخراج البيانات التي تتطلب التكامل. على سبيل المثال، أثناء تشغيل المحفوظات أو أدوات النسخ الاحتياطي.
  • EPIPE من الأفضل أن نتجاهل عند عرض المعلومات والرسائل المساعدة. على سبيل المثال، عند عرض معلومات حول الخيارات --help أو --version.
  • إذا كان من الممكن استخدام الكود الذي يتم تطويره في خط أنابيب من قبل | headثم EPIPE من الأفضل التجاهل، وإلا فمن الأفضل المعالجة والتفكير في حالة الخروج.

وأود أن أغتنم هذه الفرصة لأعرب عن امتناني للفرق MCST и التلينكس لعمل منتج عظيم . إصرارك مذهل!
يبقيه حتى الرفاق، حتى اجتماعات في الخريف!

شكرا بيريز لتصحيح الأخطاء المطبعية والأخطاء.
KDPV من جورجي أ.

المصدر: www.habr.com

إضافة تعليق