En till Heisenbug förbi krokodilen

En till Heisenbug förbi krokodilen

$> set -o pipefail

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

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

Här fortune villkorligt program utan exit(rand()).

Kan du förklara? vad är det för fel här?

Lyrisk-historisk utvikning

Jag blev först bekant med denna Heisenbug för ett kvarts sekel sedan. Sedan för gatewayen i FaxNET var det nödvändigt att skapa flera verktyg via Rören "spela pjäs" under FreeBSD. Som väntat ansåg jag mig vara en avancerad och ganska erfaren programmerare. Därför tänkte jag göra allt så noggrant och noggrant som möjligt, med särskild uppmärksamhet på felhantering...

Min tidigare erfarenhet av att hantera buggar i sendmail och uucp/uupc bidrog till min noggrannhet i "grundlig felhantering." Det är ingen idé att dyka in i detaljerna i den historien, men jag kämpade med denna Heisenbug i två veckor i 10-14 timmar. Därför kom det ihåg, och igår var denna gamla bekant förbi för att hälsa på igen.

TL;DR Svar

Verktyg head kan stäng kanalen från fortune genast så fort han läser första raden. Om fortune matar ut mer än en rad, sedan motsvarande anrop write() kommer att returnera ett fel eller rapportera att färre byte matas ut än begärt. I sin tur skriven med noggrann felhantering fortune har rätt att spegla denna situation i sin exitstatus. Då på grund av installation set -o pipefail träna || echo "Вы проиграли".

Emellertid head kanske inte hinner i tid nära innan fortune kommer att slutföra utmatningen av data. Då kommer det att fungera && echo "Повезло!".

I en av mina idag GNUMakefile det finns en fragment:

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

Översatt till människa

Det är vanligt här för GNU -märke и bash på kompilatorn sätt med alternativet --version den frågar vem han är, och om alternativet inte stöds, så infogas en stubb "Vänligen använd GCC- eller CLANG-kompatibel kompilator".

som Kokplatta kan hittas var som helst. Det dök upp på den här platsen för länge sedan och fungerade perfekt överallt (Linux, Solaris, OSX, FreeBSD, WSL etc.). Men igår altlinux på plattformen Elbrus 2000 (E2K) Jag märkte:

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

Uppriktigt sagt kände jag inte direkt igen min gamla "bekant". Dessutom har projektet redan testats många gånger på Elbrus och under många olika distributioner, inklusive Alt. Med olika kompilatorer, versioner av GNU Make och bash. Därför ville jag inte se mitt misstag här.

När man försökte reproducera problemet och/eller förstå vad som pågick började det hända mer konstiga saker.
Kommandoradsstavning:

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

Då och då producerade det extra text, då inte... Ofta skulle ett av alternativen hålla sig ganska länge, men om du petade längre fick du alltid båda!

Naturligtvis, strace vårt allt! Och efter att ha skrivit en strace tirade, men inte haft tid att trycka på Enter, kände jag igen min gamle vän Mr Heisenbug och utvecklarna kompilator jag själv för 25 år sedan, Nostalgi… Och jag bestämde mig för att vara ledsen och skriva den här lappen 😉

Förresten, som vilken självrespekt som helst Heisenbug, under strace föredrar att inte reproducera sig.

Så vad händer?

  • Verktyg head har rätt (eller snarare till och med tvingad) att stänga kanalen som läses så snart den läser det begärda antalet rader.
  • Den datagenererande programskrivaren (i detta fall cc) kan skriva ut flera rader och fri gör detta genom flera samtal write().
  • Om läsaren kommer att hinna stänga kanalen på sin sida innan inspelningen är slut på skribentens sida, då får skribenten ett felmeddelande.
  • Författarprogram berättigad både ignorera kanalskrivfelet och återspegla det i din slutförandekod.
  • På grund av installation set -o pipefail pipeline-kompletteringskoden kommer att vara icke-noll (felaktig) om resultatet är icke-noll från minst ett element, och då kommer det att fungera || echo "Please use GCC or CLANG compatible compiler".

Det kan finnas variationer beroende på hur writerprogrammet fungerar med signaler. Till exempel kan programmet avslutas onormalt (med automatisk generering av en avslutningsstatus som inte är noll/fel), eller write() kommer att returnera resultatet av att skriva färre byte än vad som begärts och ställts in errno = EPIPE.

Vem är skyldig?

I det beskrivna fallet lite av allt. Felhantering i cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) är det inte överflödig. I många fall är det bättre att vara försiktig, även om detta avslöjar plötsliga brister i en pannplatta designad för traditionellt beteende.

Vad ska man göra?

Fel:

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

korrigera:

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

    Här kommer tidig stängning av ett rör att hanteras av kommandotolken vid service av den kapslade pipelinen ("inom" parentesen). Följaktligen, om fortune kommer att rapportera ett fel skriftligt till en stängd kanal i status, sedan utgången || echo "Ошибка" det kommer ingenstans, eftersom kanalen redan är stängd.

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

    Här är verktyget cat fungerar som en dämpare eftersom den ignorerar felet EPIPE vid uttag. Detta räcker för nu slutsatsen fortune liten (flera rader) och passar i kanalbufferten (från 512 byte till ≈64K, i de flesta operativsystem ≥4K). Annars kan problemet återkomma.

Hur man bearbetar korrekt EPIPE och andra inspelningsfel?

Det finns ingen enskild rätt lösning, men det finns enkla rekommendationer:

  • EPIPE krävs måste behandlas (och återspeglas i utgångsstatusen) vid utmatning av data som kräver integritet. Till exempel under driften av arkiveringsverktyg eller säkerhetskopieringsverktyg.
  • EPIPE bättre att ignorera vid visning av information och extrameddelanden. Till exempel när du visar information om alternativ --help eller --version.
  • Om koden som utvecklas kan användas i en pipeline innan | head, Sedan EPIPE Det är bättre att ignorera, annars är det bättre att bearbeta och reflektera i exitstatusen.

Jag vill ta tillfället i akt att uttrycka min tacksamhet till teamen MCST и altlinux för stort produktivt arbete. Din beslutsamhet är fantastisk!
Fortsätt så Camarades, upp möten på hösten!

Tack berez för att rätta stavfel och fel.
KDPV från Georgy A.

Källa: will.com

Lägg en kommentar