یک هایزنباگ دیگر از کنار کروکودیل گذشت

یک هایزنباگ دیگر از کنار کروکودیل گذشت

$> set -o pipefail

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

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

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

می توانید توضیح دهید؟ اینجا چه اشکالی دارد?

انحراف غنایی-تاریخی

من اولین بار ربع قرن پیش با این هایزنباگ آشنا شدم. سپس برای Gateway در FaxNET لازم بود چندین ابزار از طریق ایجاد شود لوله های "بازی چکرز" تحت FreeBSD. همانطور که انتظار می رفت، من خود را یک برنامه نویس پیشرفته و نسبتاً با تجربه می دانستم. از این رو قصد داشتم هر کاری را تا حد امکان با دقت و دقت انجام دهم و توجه ویژه ای به رسیدگی به خطا داشته باشم...

تجربه قبلی من از برخورد با اشکالات در sendmail و uucp/uupc به کوشش من در "کنترل کامل خطا" افزود. غواصی در جزئیات آن داستان فایده ای ندارد، اما من دو هفته با این هایزنباگ به مدت 10-14 ساعت مبارزه کردم. از این رو به یادگار ماند و دیروز این آشنای قدیمی برای دیدار دوباره رفت.

TL;DR پاسخ

سودمندی 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')"'

به انسان ترجمه شده است

اینجا رایج است برای GNU را بسازید и بر هم زدن به روش کامپایلر با استفاده از گزینه --version می پرسد که او کیست، و اگر گزینه پشتیبانی نمی شود، یک خرد درج می شود "لطفا از کامپایلر سازگار با GCC یا CLANG استفاده کنید".

مانند تکرار واضحات را می توان در هر جایی یافت مدتها پیش در این مکان ظاهر شد و در همه جا کاملاً کار می کرد (لینوکس، سولاریس، OSX، FreeBSD، WSL و غیره.). اما دیروز در altlinux بر روی پلت فرم Elbrus 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 همه چیز ما و پس از تایپ کردن یک تایر strace، اما فرصتی برای فشار دادن Enter نداشتم، دوست قدیمی خود آقای هایزنباگ و توسعه دهندگان را شناختم. کامپایلر خودم 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 и altlinux برای کار سازنده بزرگ عزم شما شگفت انگیز است!
همینطور ادامه بده رفقا، همینطور جلسات در پاییز!

سپاس ها برز برای تصحیح اشتباهات تایپی
KDPV از جورجی ا.

منبع: www.habr.com

اضافه کردن نظر