En Heisenbug til forbi krokodillen

En Heisenbug til forbi krokodillen

$> set -o pipefail

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

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

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

Kan du forklare? hva er galt her?

Lyrisk-historisk digresjon

Jeg ble først kjent med denne Heisenbugen for et kvart århundre siden. Så for gatewayen i FaxNET var det nødvendig å lage flere verktøy via rør "spille dam" under FreeBSD. Som forventet anså jeg meg selv som en avansert og ganske erfaren programmerer. Derfor hadde jeg tenkt å gjøre alt så nøye og forsiktig som mulig, med spesiell oppmerksomhet på feilhåndtering...

Min tidligere erfaring med å håndtere feil i sendmail og uucp/uupc bidro til min omhu i "grundig feilhåndtering." Det er ingen vits i å dykke ned i detaljene i den historien, men jeg slet med denne Heisenbugen i to uker i 10-14 timer. Derfor ble det husket, og i går var denne gamle kjenningen innom på besøk igjen.

TL;DR Svar

Nytte head kan lukke kanalen fra fortune сразу så snart han leser den første linjen. Hvis fortune sender ut mer enn én linje, deretter den tilsvarende samtalen write() vil returnere en feil eller rapportere at færre byte sendes ut enn forespurt. I sin tur skrevet med forsiktig feilhåndtering fortune har rett til å reflektere denne situasjonen i sin utreisestatus. Da på grunn av installasjon set -o pipefail skal jobbe || echo "Вы проиграли".

Imidlertid head kanskje ikke rekker det i tide lukke før fortune vil fullføre utdataene. Da vil det fungere && echo "Повезло!".

I en av mine i dag GNUMakefile det er slikt fragment:

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

Oversatt til menneske

Det er vanlig her for GNU-fabrikat и bash på kompilatoren ved å bruke alternativet --version den spør hvem han er, og hvis alternativet ikke støttes, blir det satt inn en stubbe "Vennligst bruk GCC- eller CLANG-kompatibel kompilator".

Ligner på tekst kan finnes hvor som helst. Den dukket opp på dette stedet for lenge siden og fungerte perfekt overalt (Linux, Solaris, OSX, FreeBSD, WSL etc.). Men i går i altlinux på plattformen Elbrus 2000 (E2K) Jeg la merke til:

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

Helt ærlig kjente jeg ikke umiddelbart igjen min gamle "bekjent". Dessuten har prosjektet allerede blitt testet mange ganger på Elbrus og under mange forskjellige distribusjoner, inkludert Alt. Med forskjellige kompilatorer, versjoner av GNU Make og bash. Derfor ville jeg ikke se feilen min her.

Når man forsøkte å reprodusere problemet og/eller forstå hva som foregikk, begynte mer merkelige ting å skje.
Kommandolinjestavelse:

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

Nå og da ville det produsere ekstra tekst, så ikke... Ofte holdt ett av alternativene seg ganske lenge, men hvis du pirket lenger, fikk du alltid begge deler!

Selvfølgelig strace alt vårt! Og etter å ha skrevet en strace tirade, men ikke hatt tid til å trykke Enter, kjente jeg igjen min gamle venn Mr. Heisenbug og utviklerne kompilator meg selv for 25 år siden, Nostalgi ... Og jeg bestemte meg for å være trist og skrive dette notatet 😉

Forresten, som enhver selvrespekt Heisenbug, under strace foretrekker å ikke reprodusere.

Så hva skjer?

  • Nytte head har rett (eller rettere sagt, er til og med tvunget) til å lukke kanalen som leses så snart den leser det forespurte antallet linjer.
  • Den datagenererende programskriveren (i dette tilfellet cc) kan skrive ut flere linjer og gratis gjør dette gjennom flere samtaler write().
  • Hvis leseren vil ha tid til å stenge kanalen på sin side før slutten av opptaket på forfatterens side, da vil skribenten motta en feilmelding.
  • Forfatterprogram har rett både ignorerer kanalskrivefeilen og reflekterer den i fullføringskoden.
  • På grunn av installasjon set -o pipefail pipeline fullføringskoden vil være ikke-null (feil) hvis resultatet er ikke-null fra minst ett element, og da vil det fungere || echo "Please use GCC or CLANG compatible compiler".

Det kan være variasjoner avhengig av hvordan skriveprogrammet fungerer med signaler. For eksempel kan programmet avsluttes unormalt (med automatisk generering av en ikke-null/feil avslutningsstatus), eller write() vil returnere resultatet av å skrive færre byte enn forespurt og satt errno = EPIPE.

Hvem har skylden?

I det beskrevne tilfellet litt av alt. Feilhåndtering i cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) er ikke overflødig. I mange tilfeller er det bedre å feile på siden av forsiktighet, selv om dette avslører plutselige feil i en kokeplate designet for tradisjonell oppførsel.

Hva gjør jeg?

Feil:

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

korrigere:

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

    Her vil tidlig stenging av et rør håndteres av kommandotolken ved service på den nestede rørledningen ("innenfor" parentesen). Følgelig, hvis fortune vil rapportere en feil skriftlig til en lukket kanal i status, deretter utgangen || echo "Ошибка" den kommer ingen vei, siden kanalen allerede er stengt.

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

    Her er verktøyet cat fungerer som en demper fordi den ignorerer feilen EPIPE ved uttak. Dette er nok for nå konklusjon fortune liten (flere linjer) og passer i kanalbufferen (fra 512 byte til ≈64K, i de fleste operativsystemer ≥4K). Ellers kan problemet komme tilbake.

Hvordan behandle riktig EPIPE og andre opptaksfeil?

Det er ingen enkelt riktig løsning, men det er enkle anbefalinger:

  • EPIPE nødvendig må behandles (og gjenspeiles i utgangsstatusen) når du sender ut data som krever integritet. For eksempel under drift av arkivere eller sikkerhetskopieringsverktøy.
  • EPIPE bedre å ignorere når du viser informasjon og hjelpemeldinger. For eksempel når du viser informasjon om alternativer --help eller --version.
  • Hvis koden som utvikles kan brukes i en pipeline før | headderetter EPIPE Det er bedre å ignorere, ellers er det bedre å behandle og reflektere i utgangsstatusen.

Jeg vil benytte anledningen til å uttrykke min takknemlighet til lagene MCST и altlinux for flott produktivt arbeid. Din besluttsomhet er fantastisk!
Fortsett med det Camarades, opp møter om høsten!

Takk berez for å rette skrivefeil og feil.
KDPV fra Georgy A.

Kilde: www.habr.com

Legg til en kommentar