$> 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
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
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 --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
#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
Förresten, som vilken självrespekt som helst 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 samtalwrite()
. - 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:
-
(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. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Här är verktyget
cat
fungerar som en dämpare eftersom den ignorerar feletEPIPE
vid uttag. Detta räcker för nu slutsatsenfortune
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
, SedanEPIPE
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
Fortsätt så Camarades, upp
Tack
KDPV från
Källa: will.com