Уште еден Хајзенбуг покрај крокодилот

Уште еден Хајзенбуг покрај крокодилот

$> set -o pipefail

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

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

Тука fortune условна програма без exit(rand()).

Можете ли да објасните? што не е во ред тука?

Лирско-историска дигресија

Со овој Хајзенбуг првпат се запознав пред четвртина век. Потоа, за портата во 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')"'

Преведено на човечки

Тоа е вообичаено овде за Направете ГНУ и баш на компајлер начин користејќи ја опцијата --version прашува кој е тој, и ако опцијата не е поддржана, тогаш се става никулец „Ве молиме користете компајлер компатибилен со GCC или CLANG“.

како котел Можете да го најдете насекаде. Се појави на ова место одамна и работеше совршено насекаде (Linux, Соларис, OSX, FreeBSD, WSL итн.). Но вчера во AltLinux на платформата Елбрус 2000 (E2K) Забележав:

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

Искрено, не го препознав веднаш мојот стар „познаник“. Покрај тоа, проектот е веќе многу пати тестиран на Елбрус и под многу различни дистрибуции, вклучувајќи го и 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, го препознав мојот стар пријател г-дин Хајзенбаг и програмерите компајлер јас пред 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 за одлична продуктивна работа. Вашата решителност е неверојатна!
Продолжете така Камаради, така состаноци наесен!

Благодарение берез за корекција на печатни грешки и грешки.
КДПВ од Георги А.

Извор: www.habr.com

Купете доверлив хостинг за сајтови со DDoS заштита, VPS VDS сервери 🔥 Купете сигурен веб-хостинг со DDoS заштита, VPS VDS сервери | ProHoster