Ešte jeden Heisenbug za krokodílom

Ešte jeden Heisenbug za krokodílom

$> set -o pipefail

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

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

Tu fortune podmienený program bez exit(rand()).

Môžeš vysvetliť? čo je tu zle?

Lyricko-historická odbočka

Prvýkrát som sa s týmto Heisenbugom zoznámil pred štvrťstoročím. Potom pre bránu vo FaxNET bolo potrebné vytvoriť niekoľko utilít cez Rúrky „hranie dámy“ pod FreeBSD. Ako sa dalo očakávať, považoval som sa za pokročilého a pomerne skúseného programátora. Preto som mal v úmysle robiť všetko čo najopatrnejšie a najopatrnejšie, pričom som venoval osobitnú pozornosť odstraňovaniu chýb...

Moje predchádzajúce skúsenosti s riešením chýb v sendmail a uucp/uupc prispeli k mojej usilovnosti pri „dôkladnom spracovaní chýb“. Nemá zmysel ponárať sa do podrobností tohto príbehu, ale s týmto Heisenbugom som bojoval dva týždne 10-14 hodín. Preto sa na to spomenulo a včera sa tento starý známy opäť zastavil na návšteve.

Odpoveď TL;DR

Užitočnosť head plechovka zatvorte kanál z fortune сразу hneď ako prečíta prvý riadok. Ak fortune vypíše viac ako jeden riadok, potom zodpovedajúce volanie write() vráti chybu alebo hlási, že je na výstupe menej bajtov, ako sa požaduje. Na druhej strane napísané s opatrným spracovaním chýb fortune má právo zohľadniť túto situáciu vo svojom výstupnom stave. Potom kvôli inštalácii set -o pipefail bude pracovať || echo "Вы проиграли".

Avšak, head nemusí to stihnúť včas zavrieť predtým fortune dokončí výstup údajov. Potom to bude fungovať && echo "Повезло!".

V jednom z mojich dnešných GNUMakefile existuje taký fragment:

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

Preložené do ľudského jazyka

Je to tu bežné pre Značka GNU и tresnúť kompilátorovým spôsobom pomocou voľby --version spýta sa, kto je, a ak možnosť nie je podporovaná, vloží sa stub "Použite kompilátor kompatibilný s GCC alebo CLANG".

ako kotolník možno nájsť kdekoľvek. Objavil sa na tomto mieste už dávno a fungoval perfektne všade (Linux, Solaris, OSX, FreeBSD, WSL atď.). Ale včera v altlinux na platforme Elbrus 2000 (E2K) Všimol som si:

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

Úprimne povedané, svojho starého „známeho“ som hneď nespoznal. Navyše, projekt už bol mnohokrát testovaný na Elbrus a pod mnohými rôznymi distribúciami, vrátane Alt. S rôznymi kompilátormi, verziami GNU Make a bash. Preto som tu nechcel vidieť svoju chybu.

Pri pokuse reprodukovať problém a/alebo pochopiť, čo sa deje, sa začali diať podivnejšie veci.
Kúzlo príkazového riadku:

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

Z času na čas by to produkovalo text navyše, potom nie... Často by jedna z možností trvala dosť dlho, ale ak by ste štukali dlhšie, vždy ste dostali obe!

Samozrejme, strace naše všetko! A tak, keď som napísal tirádu, ale nemal som čas stlačiť Enter, spoznal som svojho starého priateľa pána Heisenbuga a vývojárov kompilátor ja pred 25 rokmi, Nostalgia… A rozhodla som sa byť smutná a napísať túto poznámku 😉

Mimochodom, ako každá sebaúcta Heisenbug, pod strace radšej sa nerozmnožuje.

Tak čo sa deje?

  • Užitočnosť head má právo (alebo skôr je dokonca nútené) zatvoriť čítaný kanál hneď, ako načíta požadovaný počet riadkov.
  • Program na vytváranie údajov (v tomto prípade cc) plechovka vytlačiť viacero riadkov a zadarmo urobte to prostredníctvom viacerých hovorov write().
  • Ak čitateľ bude mať čas zavrieť kanál na svojej strane pred koncom nahrávania na strane zapisovateľa, potom sa autorovi zobrazí chyba.
  • Spisovateľský program nárok na obaja ignorujú chybu zápisu kanála a premietnu ju do kódu dokončenia.
  • Kvôli inštalácii set -o pipefail kód dokončenia potrubia bude nenulový (chybný), ak je výsledok aspoň jedného prvku nenulový, a potom bude fungovať || echo "Please use GCC or CLANG compatible compiler".

Môžu existovať odchýlky v závislosti od toho, ako zapisovací program pracuje so signálmi. Napríklad môže dôjsť k abnormálnemu ukončeniu programu (s automatickým generovaním nenulového/chybového stavu ukončenia), príp write() vráti výsledok zápisu menšieho počtu bajtov, ako je požadované a nastavené errno = EPIPE.

Kto je na vine?

V opísanom prípade zo všetkého trochu. Spracovanie chýb v cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nie je nadbytočný. V mnohých prípadoch je lepšie pomýliť sa na strane opatrnosti, hoci to odhaľuje náhle nedostatky v štandardnom modeli navrhnutom pre tradičné správanie.

Čo robiť?

zle:

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

opraviť:

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

    V tomto prípade skoré zatvorenie potrubia bude spracované interpretérom príkazov pri obsluhe vnoreného potrubia ("v rámci" zátvoriek). V súlade s tým, ak fortune ohlási chybu v písaní na uzavretý kanál v stave, potom výstup || echo "Ошибка" nikam sa nedostane, pretože kanál je už zatvorený.

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

    Tu je pomôcka cat funguje ako tlmič, pretože ignoruje chybu EPIPE pri odvolaní. Toto na záver stačí fortune malý (niekoľko riadkov) a zmestí sa do vyrovnávacej pamäte kanála (od 512 bajtov do ≈64K, vo väčšine OS ≥4K). V opačnom prípade sa problém môže vrátiť.

Ako zvládnuť EPIPE a iné chyby pri nahrávaní?

Neexistuje jediné správne riešenie, ale existujú jednoduché odporúčania:

  • EPIPE potrebný musia byť spracované (a odráža sa v stave ukončenia) pri výstupe údajov, ktoré vyžadujú integritu. Napríklad počas prevádzky archivátorov alebo zálohovacích utilít.
  • EPIPE lepšie ignorovať pri zobrazovaní informácií a pomocných správ. Napríklad pri zobrazovaní informácií o možnostiach --help alebo --version.
  • Ak sa vyvíjaný kód môže použiť v potrubí predtým | head, Potom EPIPE Je lepšie ignorovať, inak je lepšie spracovať a premietnuť do stavu odchodu.

Chcel by som využiť túto príležitosť a poďakovať tímom MCST и altlinux za skvelú produktívnu prácu. Vaše odhodlanie je úžasné!
Len tak ďalej Camarades, hore stretnutia na jeseň!

Vďaka berez na opravu preklepov a chýb.
KDPV od Georgy A.

Zdroj: hab.com

Pridať komentár