أسفل حفرة الأرنب: قصة خطأ واحد من الورنيش - الجزء الأول

بعد ضرب أزرار لوحة المفاتيح خلال العشرين دقيقة الماضية ، كما لو كان يكتب من أجل حياته ، يلجأ ghostinushanka إليّ بنظرة غير مجنونة في عينيه وابتسامة خبيثة ، "يا صديق ، أعتقد أنني حصلت عليها.


انظر إلى هذا "- لأنه يشير إلى إحدى الشخصيات التي تظهر على الشاشة -" أراهن قبعتي الحمراء أنه إذا أضفنا ما أرسلته للتو هنا "- لأنه يشير إلى مكان آخر في الكود -" سيكون هناك لا خطأ بعد الآن. "
في حيرة قليلا ومتعب أقوم بتعديل التعبير sed الذي كنا systemctl varnish reload منذ بعض الوقت الآن ، احفظ الملف systemctl varnish reload . انتهت رسالة الخطأ ...


"هذه الرسائل الإلكترونية التي تبادلتها مع المرشح ،" يواصل زميلي ، مع تغير ابتسامته إلى ابتسامة عريضة وحقيقية ، "لقد أدهشني فجأة أن هذه هي المشكلة بالضبط ذاتها!"


كيف بدأ كل شيء


تفترض هذه المقالة بعض الألفة مع bash و awk و systemd. بعض المعرفة بالورنيش مفيد ، لكن غير مطلوب.
تم تنقيح الطوابع الزمنية في مثال المقتطفات.
شارك في تأليفه مع ghostinushanka .


تشرق الشمس من خلال النوافذ ذات الحجم على الحائط في صباح خريف دافئ آخر ، فنجان من السائل الطازج المحتوي على الكافيين يجلس على جانب لوحة المفاتيح ، وتغني سماعات الرأس عن السمفونية المحببة للأصوات التي تغطي حفيف لوحات المفاتيح الميكانيكية حولها وأول إدخال للتراكم على kanban يعرض هزلي عنوان التذكرة المصيرية "Investigate varnishreload sh: echo: I/O error in staging". عندما يتعلق الأمر بالورنيش ، لا يوجد مجال للخطأ (الأخطاء) ، على الرغم من أن هذا الخطأ بالذات لا يبدو أنه يسبب أي مشاكل فعلية.


بالنسبة لأولئك منكم غير المطلعين على الورنيش ، إنه مجرد برنامج نصي يستخدم لإعادة تحميل التكوين - يُطلق عليه أيضًا اسم VCL - لخادم التخزين المؤقت للورنيش .


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


ضرب الحائط في 200kph


عند فتح ملف varnishreload على أحد الخوادم المتأثرة التي تعمل على امتداد Debian ، أجد varnishreload أقل من 200 سطر. من خلال القراءة لفترة وجيزة ، لا أرى شيئًا خطيرًا يمنعني من تشغيل البرنامج النصي من المحطة مرارًا وتكرارًا. بعد كل شيء ، هذا انطلاق ، حتى لو انهار ، لن يشتكي أحد ، حسنًا ... ليس كثيرًا ، هذا هو الحال. أقوم بتشغيل البرنامج النصي ولاحظ ، فقط لمعرفة أنه لا توجد أخطاء يمكن رؤيتها. يتم تشغيل عدة مرات متكررة للتأكد بشكلٍ معقول من أنني لا أستطيع إعادة إنتاج الخطأ دون بذل أي جهد إضافي وأبدأ في وضع خطط لتعديل بيئة البرنامج النصي وثنيها. هل إغلاق STDOUT للنص البرمجي بالكامل (مع > &- ) يساعد أي شيء؟ أو stderr؟ لا فعلت.


من الواضح أن systemd يشوه البيئة بطريقة ما ، لكن كيف و ... لماذا؟ أطلق النار على vim وتعديل varnishreload الخاص varnishreload ، مضيفًا set -x مباشرة أسفل shebang ، آملًا أن varnishreload إخراج البرنامج النصي المفصل بعض الضوء.


الملف مُرقَّع ، لذا أعد تحميل الورنيش ، فقط لأرى أن التغيير قد كسر النص البرمجي بالكامل ... الإخراج عبارة عن فوضى كاملة تعرض أطنانًا من الكود على النمط C وأن المخزن المؤقت الافتراضي للرجوع لا يكفي للعثور على مصدره. اشعر بالارتباك هل يمكن أن يؤدي تعيين خيار تصحيح البرنامج النصي shell إلى كسر البرنامج الذي يدعو إليه؟ لا ، لا يمكن أن يكون. حشرة في الصدفة؟ سيناريوهات متعددة ممكنة تعمل بعنف في اتجاهات مختلفة في ذهني. انتهى على الفور كوب من المشروبات التي تحتوي على الكافيين ، رحلة سريعة إلى المطبخ لإعادة الملء وهنا نذهب مرة أخرى. أفتح الملف وننظر عن كثب إلى shebang: #!/bin/sh .


لكن /bin/sh هو بالتأكيد رابط إلى bash ، بحيث يتم تفسير البرنامج النصي في وضع متوافق مع POSIX ، أليس كذلك؟ ! الخطأ القشرة الافتراضية غير التفاعلية على دبيان هي شرطة ، وهذا بالضبط ما يشير إليه /bin/sh .


 # ls -l /bin/sh lrwxrwxrwx 1 root root 4 Jan 24 2017 /bin/sh -> dash 

إذا فقط لتصحيح الأخطاء ، قمت بتغيير shebang إلى #!/bin/bash ، وقمت بإزالة set -x وحاولت مرة أخرى. أخيرًا ، ناتج خطأ معقول من إعادة تحميل الورنيش التالي:


 Jan 01 12:00:00 hostname varnishreload[32604]: /usr/sbin/varnishreload: line 124: echo: write error: Broken pipe Jan 01 12:00:00 hostname varnishreload[32604]: VCL 'reload_20190101_120000_32604' compiled 

السطر 124 ، الآن نحن نتحدث!


 114 find_vcl_file() { 115 VCL_SHOW=$(varnishadm vcl.show -v "$VCL_NAME" 2>&1) || : 116 VCL_FILE=$( 117 echo "$VCL_SHOW" | 118 awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}' | { 119 # all this ceremony to handle blanks in FILE 120 read -r DELIM VCL_SHOW INDEX SIZE FILE 121 echo "$FILE" 122 } 123 ) || : 124 125 if [ -z "$VCL_FILE" ] 126 then 127 echo "$VCL_SHOW" >&2 128 fail "failed to get the VCL file name" 129 fi 130 131 echo "$VCL_FILE" 132 } 

لكن كما اتضح ، فإن الخط 124 غير هادئ. لا يمكنني إلا أن أظن أن الخطأ قد تم إنتاجه كجزء من تنفيذ أمر متعدد الخطوط في السطر 116.


إذن ما الذي ينتج عنه الجزء الفرعي أعلاه للتخزين في المتغير VCL_FILE ؟ في الجزء الأول ، يرسل محتويات متغير VCL_SHOW تم إنشاؤه على السطر 115 في الأنبوب. ماذا يحدث هناك ، إذن؟


أولاً ، يستخدم varnishadm ، وهو جزء قياسي من تثبيت الورنيش المستخدم لتكوين الورنيش دون الحاجة إلى إعادة تشغيله. يتم استخدام الأمر الفرعي vcl.show -v لطباعة تكوين VCL بالكامل المحدد بواسطة ${VCL_NAME} إلى STDOUT.


لعرض تكوين VCL النشط الحالي بالإضافة إلى العديد من الإصدارات السابقة من توجيه الورنيش التي لا تزال في الذاكرة ، يمكنك استخدام varnishadm vcl.list ، والذي سيكون إنتاجه مشابهًا لما يلي:


 discarded cold/busy 1 reload_20190101_120000_11903 discarded cold/busy 2 reload_20190101_120000_12068 discarded cold/busy 16 reload_20190101_120000_12259 discarded cold/busy 16 reload_20190101_120000_12299 discarded cold/busy 28 reload_20190101_120000_12357 active auto/warm 32 reload_20190101_120000_12397 available auto/warm 0 reload_20190101_120000_12587 

يتم تعيين المتغير ${VCL_NAME} في مكان آخر في البرنامج النصي varnishreload إلى اسم VCL النشط حاليًا ، إن وجد. في هذه الحالة ، سيكون ذلك "reload_20190101_120000_12397".


رائع ، لذا ${VCL_SHOW} يحتوي الآن على تهيئة كاملة ${VCL_SHOW} ، وهو سهل بدرجة كافية حتى الآن. الآن فهمت أخيرًا السبب في أن ناتج set -x مع set -x بدا أنه مكسور للغاية - فقد تضمن محتويات تكوين الورنيش الناتج.


الشيء المهم هنا هو أن تكوين VCL الكامل قد يتم ربطه معًا في كثير من الأحيان من ملفات متعددة. تُستخدم التعليقات ذات النمط C لتحديد المكان الذي تم فيه تضمين ملفات التكوين في ملفات التكوين الأخرى ، وهو بالضبط ما يدور حوله السطر التالي من مقتطف الشفرة.


يحتوي بناء جملة التعليقات المحددة للملف على التنسيق التالي


 // VCL.SHOW <NUM> <NUM> <FILENAME> 

الأرقام ليست مهمة هنا ، ما يهمنا هو اسم الملف.


إذن ما الذي يحدث في العالم في عدد كبير من الأوامر التي تبدأ على الخط 116؟
دعونا نلتقطها.
هناك أربعة أجزاء للأمر:


  1. echo بسيط يطبع قيمة ${VCL_SHOW}
     echo "$VCL_SHOW" 
  2. awk الذي يبحث عن خط (سجل) حيث الحقل الأول هو "//" والثاني هو "VCL.SHOW".
    يُطلب من Awk طباعة السطر الأول المطابق لهذه الأنماط ثم إيقاف المعالجة فورًا.
     awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}' 
  3. كتلة تعليمات برمجية تُقرأ في الحقول ذات المسافات البيضاء إلى خمسة متغيرات. يحصل المتغير الخامس FILE على بقية السطر. أخيرًا ، يقوم صدى آخر بطباعة محتويات المتغير ${FILE} .
     { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }. 
  4. نظرًا لأن جميع الخطوات من 1 إلى 3 مغطاة في مجلد فرعي ، سينتهي إخراج $FILE في المتغير VCL_FILE .

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


لقد علقت بمنطق المعالجة الأصلي لـ ${VCL_FILE} وحاولت تعديل سلسلة الأوامر ولكن بدون حد معقول. كل شيء يعمل في صدفي ولكن أبدا عندما تعمل كخدمة.


يبدو أن الخطأ ليس قابلاً للتكرار على الإطلاق عندما أديره - في غضون ذلك ، مرت 30 دقيقة تقريبًا ست مرات ومهمة جديدة ذات أولوية عليا تضع كل شيء جانباً. كانت بقية الأسبوع مليئة بمهام مختلفة ، والاستثناءان هما حديث داخلي لفريقنا حول استخدام sed ومقابلة مع مرشح واعد. المشكلة مع جعل الخطأ varnishreload تختفي تماما لرمال الوقت.


الخاص بك ما يسمى فو فو ... هو حقا ... مثير للشفقة جدا


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


نظرًا لأن ثني الرمز في المرة الأخيرة لم يساعد ، فقد اخترت فقط إعادة كتابة السطر 116. وكان الرمز الحالي مجنونًا ، على أي حال. ليست هناك حاجة مطلقًا لاستخدام read هنا.


النظر في الخطأ مرة أخرى:
sh: echo: broken pipe - echo موجود في مكانين في هذا الأمر ، لكنني أظن أن أول واحد يكون أكثر جُرّاء (أو شريك). Awk لا يوحي بالثقة أيضا. حسنًا ، في حال كان الأمر حقًا awk | {read; echo} awk | {read; echo} awk | {read; echo} بناء يسبب كل هذه المشاكل ، لماذا لا تستخدم أي شيء آخر؟ لا تستخدم Awk حقًا لقدراتها الكاملة على هذا الخط المفرد وبعد ذلك هناك read فائضة.


عندما رأينا حديثًا داخليًا حول sed في الأسبوع الآخر ، أردت تجربة مهاراتي المكتسبة حديثًا وتحسين echo | awk | { read; echo } echo | awk | { read; echo } echo | awk | { read; echo } في echo | sed أبسط echo | sed echo | sed . على الرغم من أن هذه ليست بالتأكيد الطريقة الصحيحة للتعامل مع تصحيح الأخطاء ، إلا أنني اعتقدت أنني على الأقل سأحاول تجربة استخدام جهاز sed-fu وربما أتعلم شيئًا جديدًا حول المشكلة في العملية. في هذه العملية ، طلبت من زميلي - مؤلف خطاب sed - مساعدتي في التوصل إلى أمر sed أكثر كفاءة.


لقد ألقيت varnishadm vcl.show -v "$VCL_NAME" في ملف ، لذلك يمكنني التركيز على كتابة sed بدون كل المتاعب حول إعادة تحميل الخدمة.


يمكن العثور على كتاب تمهيدي قصير حول كيفية إدخال عمليات sed بالضبط في دليل جنو الخاص بها . في مصادر المصادر ، يتم تحديد الحرف \n بشكل صريح باعتباره فاصل الأسطر.


بعد عدة تكرارات ومدخلات من زميلي ، قمنا بصياغة تعبير sed الذي أنتج نفس النتيجة تمامًا مثل السطر الأصلي 116.


لنقم بإنشاء ملف إدخال نموذج هنا ،


 > cat vcl-example.vcl Text // VCL.SHOW 0 1578 file with 3 spaces.vcl More text // VCL.SHOW 0 1578 file.vcl Even more text // VCL.SHOW 0 1578 file with TWOspaces.vcl Final text 

قد لا يكون ذلك واضحًا من الوصف أعلاه ، لكننا مهتمون فقط // VCL.SHOW الأول ، وقد يكون هناك العديد من المدخلات. هذا هو بالضبط سبب استقالة awk بعد المباراة الأولى.


 # step 1, capture just the comment lines # using sed capability to specify delimiter character with '\#' instead of the commonly used '/' so there is no need to escape slashes themselves # and the “address” capability defined as regex “// VCL.SHOW” to search for lines with specific pattern # -n flag makes sure that the sed does not print all as it does by default (see above link) # -E switches to the extended regex > cat vcl-processor-1.sed \#// VCL.SHOW#p > sed -En -f vcl-processor-1.sed vcl-example.vcl // VCL.SHOW 0 1578 file with 3 spaces.vcl // VCL.SHOW 0 1578 file.vcl // VCL.SHOW 0 1578 file with TWOspaces.vcl # step 2, only print out the file name # using the “substitute” command with regex capture groups to print just that group # and this is done only for the matches of the previous search > cat vcl-processor-2.sed \#// VCL.SHOW# { s#.* [0-9]+ [0-9]+ (.*)$#\1# p } > sed -En -f vcl-processor-2.sed vcl-example.vcl file with 3 spaces.vcl file.vcl file with TWOspaces.vcl # step 3, make sure to only get the first result # same as with the awk before, add an immediate exit after the first processed match is printed > cat vcl-processor-3.sed \#// VCL.SHOW# { s#.* [0-9]+ [0-9]+ (.*)$#\1# p q } > sed -En -f vcl-processor-3.sed vcl-example.vcl file with 3 spaces.vcl # step 4, wrap it up into a one-liner using the colon to separate commands > sed -En -e '\#// VCL.SHOW#{s#.* [0-9]+ [0-9]+ (.*)$#\1#p;q;}' vcl-example.vcl file with 3 spaces.vcl 

لذلك ، فإن محتويات البرنامج النصي varnishreload ستبدو مثل هذا:


 VCL_FILE="$(echo "$VCL_SHOW" | sed -En '\#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#\1#p;q;};')" 

يمكن التعبير عن المنطق أعلاه بإيجاز بواسطة:
إذا كان سطر ما يطابق regex // VCL.SHOW ، // VCL.SHOW بمطابقة النص // VCL.SHOW بما في ذلك الرقمان على ذلك السطر والتقاط أي شيء يأتي بعد ذلك. تنبعث من القبض على وإنهاء.


بسيط ، أليس كذلك؟


كنا سعداء بالبرنامج النصي sed ، varnishreload على الرمز الأصلي الذي يحل محله ، كل اختبارات التشغيل التي أجريتها أنتجت النتائج المرجوة ، لذلك قمت بتعديل varnishreload على الخادم وأطلقت systemctl reload varnish مرة أخرى. echo: write error: Broken pipe اللعين echo: write error: Broken pipe كان يبتسم في وجوهنا. ينتظر المؤشر الوامض إدخال أمر جديد في الفراغ المظلم من الجهاز ...

Source: https://habr.com/ru/post/ar475698/


All Articles