Dar vienas Heisenbug pro krokodilą

Dar vienas Heisenbug pro krokodilą

$> set -o pipefail

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

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

Čia fortune sąlyginė programa be exit(rand()).

Ar gali paaiškinti? kas čia negerai?

Lyrinis-istorinis nukrypimas

Pirmą kartą su šiuo Heisenbug susipažinau prieš ketvirtį amžiaus. Tada FaxNET šliuzui reikėjo sukurti keletą komunalinių paslaugų per vamzdžiai "žaisti šaškėmis" pagal FreeBSD. Kaip ir tikėjausi, laikau save pažengusiu ir pakankamai patyrusiu programuotoju. Todėl ketinau viską daryti kuo kruopščiau ir atidžiau, ypatingą dėmesį skirdamas klaidų valdymui...

Mano ankstesnė patirtis, susijusi su „sendmail“ ir „uucp/uupc“ klaidomis, padidino mano kruopštumą „kruopštaus klaidų tvarkymo“ srityje. Nėra prasmės gilintis į tos istorijos detales, bet aš su šiuo Heisenbug kovojau dvi savaites 10–14 valandų. Todėl buvo prisiminta, o vakar šis senas pažįstamas vėl užsuko aplankyti.

TL;DR Atsakymas

Naudingumas head galima uždarykite kanalą nuo fortune сразу kai tik perskaitys pirmąją eilutę. Jeigu fortune išveda daugiau nei vieną eilutę, tada atitinkamas skambutis write() grąžins klaidą arba praneš, kad išvedama mažiau baitų, nei reikalaujama. Savo ruožtu parašyta atsargiai elgiantis su klaidomis fortune turi teisę šią situaciją atspindėti savo išėjimo būsenoje. Tada dėl įrengimo set -o pipefail dirbs || echo "Вы проиграли".

Tačiau head gali nespėti laiku uždaryti prieš tai fortune baigs išvesti duomenis. Tada jis veiks && echo "Повезло!".

Viename iš mano šiandienos GNUMakefile yra tokia fragmentas:

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

Išversta į žmogų

Čia įprasta GNU gamintojas и bash kompiliatoriaus būdu naudojant parinktį --version klausia, kas jis toks, o jei parinktis nepalaikoma, tada įterpiamas stuburas "Prašome naudoti GCC arba CLANG suderinamą kompiliatorių".

kaip katilas galima rasti bet kur. Jis atsirado šioje vietoje seniai ir puikiai veikė visur (Linux, Solaris, OSX, FreeBSD, WSL ir tt). Bet vakar į „AltLinux“ ant platformos Elbrus 2000 (E2K) Pastebėjau:

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

Atvirai kalbant, aš ne iš karto atpažinau savo seną „pažįstamą“. Be to, projektas jau daug kartų buvo išbandytas Elbrus ir daugelyje skirtingų platinimų, įskaitant Alt. Su įvairiais kompiliatoriais, GNU Make ir bash versijomis. Todėl nenorėjau čia matyti savo klaidos.

Bandant atkurti problemą ir (arba) suprasti, kas vyksta, ėmė dėtis keistesni dalykai.
Komandinės eilutės rašyba:

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

Karts nuo karto sukurdavo papildomo teksto, tada ne... Dažnai vienas iš variantų užstrigdavo gana ilgai, bet jei kišdavo ilgiau, visada gaudavo abu!

Žinoma, strace mūsų viskas! Ir įvedęs trumpąją tiradą, bet nespėjęs paspausti Enter, atpažinau savo seną draugą poną Heisenbugą ir kūrėjus kompiliatorius aš prieš 25 metus, Nostalgija… O aš nusprendžiau nuliūdinti ir parašyti šį užrašą 😉

Beje, kaip ir kiekvienas save gerbiantis Heisenbugas, pagal strace nemėgsta daugintis.

Taigi kas vyksta?

  • Naudingumas head turi teisę (tiksliau, net priverstas) uždaryti skaitomą kanalą, kai tik jis nuskaito pageidaujamą eilučių skaičių.
  • Duomenis generuojanti programa-rašytojas (šiuo atveju cc) galima spausdinti kelias eilutes ir Laisvas tai padaryti per kelis skambučius write().
  • jei skaitytojas turės laiko uždaryti kanalą savo pusėje iki įrašymo pabaigos rašytojo pusėje, tada rašytojas gaus klaidą.
  • Rašytojo programa turintis teisę abu ignoruoja kanalo rašymo klaidą ir atspindi ją užbaigimo kode.
  • Dėl įrengimo set -o pipefail konvejerio užbaigimo kodas bus ne nulis (klaidingas), jei bent vieno elemento rezultatas yra ne nulis, ir tada jis veiks || echo "Please use GCC or CLANG compatible compiler".

Gali būti skirtumų, atsižvelgiant į tai, kaip rašymo programa veikia su signalais. Pavyzdžiui, programa gali baigtis nenormaliai (automatiškai sugeneravus ne nulio / klaidos nutraukimo būseną) arba write() grąžins rezultatą, kai bus įrašyta mažiau baitų nei prašoma ir nustatyta errno = EPIPE.

Kas yra kaltas?

Aprašytu atveju visko po truputį. Klaida tvarkant cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nėra perteklinis. Daugeliu atvejų geriau apsisaugoti, nors tai atskleidžia staigius tradiciniam elgesiui sukurtos sistemos trūkumus.

Ką daryti?

Neteisinga:

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

Teisingai:

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

    Čia ankstyvą vamzdžio uždarymą tvarkys komandų interpretatorius, kai aptarnauja įdėtą dujotiekį („skliausteliuose“). Atitinkamai, jei fortune praneš apie klaidą rašant į uždarą kanalą būsenoje, tada išvestis || echo "Ошибка" jis niekur nepateks, nes kanalas jau uždarytas.

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

    Čia yra naudingumas cat veikia kaip slopintuvas, nes nepaiso klaidos EPIPE pasitraukus. Dabar to pakanka išvadai fortune mažas (kelios eilutės) ir telpa į kanalo buferį (nuo 512 baitų iki ≈64K, daugumoje OS ≥4K). Priešingu atveju problema gali grįžti.

Kaip teisingai apdoroti EPIPE ir kitų įrašymo klaidų?

Vieno teisingo sprendimo nėra, tačiau yra paprastos rekomendacijos:

  • EPIPE reikia turi būti apdorotas (ir atsispindi išėjimo būsenoje), kai išvedami duomenys, kuriems reikia vientisumo. Pavyzdžiui, veikiant archyvatoriams ar atsarginėms komunalinėms programoms.
  • EPIPE geriau ignoruoti kai rodoma informacija ir pagalbiniai pranešimai. Pavyzdžiui, kai rodoma informacija apie parinktis --help arba --version.
  • Jei kuriamas kodas gali būti naudojamas anksčiau | head, Tada EPIPE Geriau ignoruoti, kitaip geriau apdoroti ir atspindėti išėjimo būseną.

Naudodamasis proga norėčiau padėkoti komandoms MCST и „AltLinux“ už puikų produktyvų darbą. Jūsų ryžtas nuostabus!
Taip ir toliau, Camarades susitikimai rudenį!

Ačiū berez rašybos ir klaidų taisymui.
KDPV iš Georgijus A.

Šaltinis: www.habr.com

Добавить комментарий