Endnu en Heisenbug forbi krokodillen

Endnu en Heisenbug forbi krokodillen

$> set -o pipefail

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

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

Her fortune betinget program uden exit(rand()).

Kan du forklare? hvad er der galt her?

Lyrisk-historisk digression

Jeg stiftede bekendtskab med denne Heisenbug for et kvart århundrede siden. Så for gatewayen i FaxNET var det nødvendigt at oprette flere hjælpeprogrammer via rør "spiller dam" under FreeBSD. Som forventet betragtede jeg mig selv som en avanceret og ret erfaren programmør. Derfor havde jeg til hensigt at gøre alt så omhyggeligt og omhyggeligt som muligt, med særlig opmærksomhed på fejlhåndtering...

Min tidligere erfaring med at håndtere fejl i sendmail og uucp/uupc tilføjede til min omhu i "grundig fejlhåndtering." Det nytter ikke noget at dykke ned i detaljerne i den historie, men jeg kæmpede med denne Heisenbug i to uger i 10-14 timer. Derfor blev det husket, og i går var denne gamle kending forbi på besøg igen.

TL;DR Svar

Hjælpeprogram head kan lukke kanalen fra fortune på én gang så snart han læser den første linje. Hvis fortune udsender mere end én linje, derefter det tilsvarende opkald write() vil returnere en fejl eller rapportere, at der udlæses færre bytes end anmodet. Til gengæld skrevet med omhyggelig fejlhåndtering fortune har ret til at afspejle denne situation i sin exitstatus. Så på grund af installation set -o pipefail vil arbejde || echo "Вы проиграли".

Imidlertid head når det måske ikke i tide lukke før fortune afslutter udlæsningen af ​​data. Så vil det virke && echo "Повезло!".

I en af ​​mine i dag GNUMakefile der er en fragment:

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

Oversat til menneske

Det er almindeligt her for GNU Make и bash på compiler måde ved hjælp af muligheden --version den spørger, hvem han er, og hvis muligheden ikke understøttes, så indsættes en stub "Brug venligst GCC eller CLANG kompatibel compiler".

lignende kedelplade kan findes overalt. Det dukkede op på dette sted for lang tid siden og fungerede perfekt overalt (Linux, Solaris, OSX, FreeBSD, WSL etc.). Men i går kl AltLinux på platformen Elbrus 2000 (E2K) Det lagde jeg mærke til:

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

Helt ærligt genkendte jeg ikke straks min gamle "bekendt". Desuden er projektet allerede blevet testet mange gange på Elbrus og under en masse forskellige distributioner, herunder Alt. Med forskellige compilere, versioner af GNU Make og bash. Derfor ville jeg ikke se min fejl her.

Når man forsøgte at reproducere problemet og/eller forstå, hvad der foregik, begyndte der at ske flere mærkelige ting.
Kommandolinjestavelse:

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

I ny og næ ville det producere ekstra tekst, så ikke... Ofte ville en af ​​mulighederne holde sig ret længe, ​​men hvis du skød længere, fik du altid begge dele!

Naturligvis strace vores alt! Og efter at have skrevet en stribe tirade, men ikke haft tid til at trykke på Enter, genkendte jeg min gamle ven hr. Heisenbug og udviklerne compiler mig selv for 25 år siden, Nostalgi ... Og jeg besluttede at være ked af det og skrive denne note 😉

Forresten, som enhver selvrespekt Heisenbug, under strace foretrækker ikke at reproducere.

Så hvad sker der?

  • Hjælpeprogram head har ret (eller rettere, er endda tvunget) til at lukke den kanal, der læses, så snart den læser det ønskede antal linjer.
  • Den datagenererende programskriver (i dette tilfælde cc) kan udskrive flere linjer og gratis gøre dette gennem flere opkald write().
  • Hvis læseren vil have tid til at lukke kanalen på sin side inden afslutningen af ​​optagelsen på forfatterens side, så vil skribenten modtage en fejl.
  • Forfatter program berettiget både ignorerer kanal skrivefejlen og afspejler den i din færdiggørelseskode.
  • På grund af installation set -o pipefail pipeline-fuldførelseskoden vil være ikke-nul (fejl), hvis resultatet er ikke-nul fra mindst ét ​​element, og så vil det fungere || echo "Please use GCC or CLANG compatible compiler".

Der kan være variationer afhængigt af, hvordan forfatterprogrammet arbejder med signaler. Programmet kan f.eks. afslutte unormalt (med automatisk generering af en ikke-nul/fejl afslutningsstatus), eller write() vil returnere resultatet af at skrive færre bytes end anmodet og indstillet errno = EPIPE.

Hvem har skylden?

I det beskrevne tilfælde lidt af hvert. Fejlhåndtering i cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) er ikke overflødig. I mange tilfælde er det bedre at tage fejl af forsigtighed, selvom dette afslører pludselige fejl i en kedelplade designet til traditionel adfærd.

Hvad skal jeg gøre?

Forkert:

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

korrigere:

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

    Her vil tidlig lukning af et rør blive håndteret af kommandofortolkeren ved servicering af den indlejrede rørledning ("indenfor" parentesen). Følgelig, hvis fortune vil rapportere en fejl skriftligt til en lukket kanal i status, derefter output || echo "Ошибка" det kommer ingen vegne, da kanalen allerede er lukket.

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

    Her er værktøjet cat fungerer som en dæmper, fordi den ignorerer fejlen EPIPE ved tilbagetrækning. Dette er nok for nu konklusion fortune lille (flere linjer) og passer i kanalbufferen (fra 512 bytes til ≈64K, i de fleste OS ≥4K). Ellers kan problemet vende tilbage.

Sådan behandles korrekt EPIPE og andre optagelsesfejl?

Der er ingen enkelt rigtig løsning, men der er enkle anbefalinger:

  • EPIPE påkrævet skal behandles (og afspejles i exit-status), når der udlæses data, der kræver integritet. For eksempel under driften af ​​arkiveringsprogrammer eller backup-værktøjer.
  • EPIPE bedre at ignorere ved visning af information og hjælpemeddelelser. For eksempel ved visning af information om muligheder --help eller --version.
  • Hvis koden, der udvikles, kan bruges i en pipeline før | headderefter EPIPE Det er bedre at ignorere, ellers er det bedre at behandle og reflektere i exitstatus.

Jeg vil gerne benytte lejligheden til at udtrykke min taknemmelighed over for holdene MCST и AltLinux for stort produktivt arbejde. Din beslutsomhed er fantastisk!
Fortsæt med det Camarades, op møder i efteråret!

Tak berez til at rette tastefejl og fejl.
KDPV fra Georgy A.

Kilde: www.habr.com

Tilføj en kommentar