Vielä yksi Heisenbug krokotiilin ohi

Vielä yksi Heisenbug krokotiilin ohi

$> set -o pipefail

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

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

Täällä fortune ehdollinen ohjelma ilman exit(rand()).

Voitko selittää? mikä tässä on vialla?

Lyyris-historiallinen poikkeama

Tutustuin tähän Heisenbugiin ensimmäisen kerran neljännesvuosisata sitten. Sitten FaxNETin yhdyskäytävää varten oli tarpeen luoda useita apuohjelmia kautta putket "pelaa nappulaa" FreeBSD:n alla. Kuten odotettiin, pidin itseäni edistyneenä ja melko kokeneena ohjelmoijana. Siksi aioin tehdä kaiken mahdollisimman huolellisesti ja huolellisesti kiinnittäen erityistä huomiota virheiden käsittelyyn...

Aiempi kokemukseni sendmail- ja uucp/uupc-virheiden käsittelystä lisäsi huolellisuuttani " perusteellisessa virheenkäsittelyssä ". Ei ole mitään järkeä sukeltaa tuon tarinan yksityiskohtiin, mutta kamppailin tämän Heisenbugin kanssa kaksi viikkoa 10-14 tuntia. Siksi se muistettiin, ja eilen tämä vanha tuttava poikkesi käymään uudelleen.

TL;DR Vastaus

Apuohjelma head voida sulje kanava alkaen fortune сразу heti kun hän lukee ensimmäisen rivin. Jos fortune tulostaa useamman kuin yhden rivin, sitten vastaavan puhelun write() palauttaa virheilmoituksen tai raportoi, että vähemmän tavuja tulostetaan kuin pyydetään. Puolestaan ​​kirjoitettu huolellisella virheenkäsittelyllä fortune on oikeus esittää tämä tilanne poistumistilassaan. Siis asennuksen takia set -o pipefail toimii || echo "Вы проиграли".

Kuitenkin, head ei ehkä ehdi ajoissa sulje ennen fortune lopettaa tietojen tulostamisen. Sitten se toimii && echo "Повезло!".

Yhdessä tämän päivän GNUMakefile sellainen on olemassa kappale:

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

Käännetty ihmiseksi

Se on yleistä täällä GNU-merkki и kemut kääntäjän tavalla käyttämällä vaihtoehtoa --version se kysyy, kuka hän on, ja jos vaihtoehtoa ei tueta, lisätään tynkä "Käytä GCC- tai CLANG-yhteensopivaa kääntäjää".

kuten kattilalevy löytyy mistä tahansa. Se ilmestyi tähän paikkaan kauan sitten ja toimi täydellisesti kaikkialla (Linux, Solaris, OSX, FreeBSD, WSL jne.). Mutta eilen sisään altlinux alustalla Elbrus 2000 (E2K) Minä huomasin:

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

Suoraan sanottuna en heti tunnistanut vanhaa "tuttavaani". Lisäksi projektia on testattu jo monta kertaa Elbruksella ja useilla eri jakeluilla, mukaan lukien Alt. Eri kääntäjillä, GNU Maken ja bashin versioilla. Siksi en halunnut nähdä virhettäni tässä.

Kun yritettiin toistaa ongelmaa ja/tai ymmärtää, mitä oli tekeillä, alkoi tapahtua enemmän outoja asioita.
Komentorivin loitsu:

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

Välillä se tuotti ylimääräistä tekstiä, sitten ei... Usein jompikumpi vaihtoehdoista jäi kiinni melko pitkäksi aikaa, mutta jos nyökkäsi pidempään, niin sai aina molemmat!

Tietenkin, strace meidän kaikkemme! Ja kun olin kirjoittanut strace-tiraadin, mutta en ehtinyt painaa Enter, tunnistin vanhan ystäväni herra Heisenbugin ja kehittäjät kääntäjä itse 25 vuotta sitten, Nostalgia ... Ja päätin olla surullinen ja kirjoittaa tämän muistiinpanon 😉

Muuten, kuten kuka tahansa itseään kunnioittava Heisenbug, alla strace ei halua lisääntyä.

Joten mitä tapahtuu?

  • Apuohjelma head on oikeus (tai pikemminkin jopa pakotettu) sulkea luettava kanava heti, kun se lukee pyydetyn määrän rivejä.
  • Tietoa luova ohjelman kirjoittaja (tässä tapauksessa cc) voida tulostaa useita rivejä ja vapaa tee tämä useiden puhelujen kautta write().
  • Jos lukijalla on aikaa sulkea kanava puoleltaan ennen kirjoittajan puolen tallennuksen päättymistä, jolloin kirjoittaja saa virheilmoituksen.
  • Kirjoittaja ohjelma on oikeus molemmat jättävät huomioimatta kanavan kirjoitusvirheen ja heijastavat sen viimeistelykoodissasi.
  • Asennuksen vuoksi set -o pipefail liukuhihnan valmistumiskoodi on nollasta poikkeava (virheellinen), jos tulos on muu kuin nolla vähintään yhdestä elementistä, ja sitten se toimii || echo "Please use GCC or CLANG compatible compiler".

Saattaa olla vaihtelua riippuen siitä, kuinka kirjoitusohjelma toimii signaalien kanssa. Ohjelma voi esimerkiksi päättyä epänormaalisti (muun kuin nolla/virhe lopetustilan automaattisen luomisen yhteydessä) tai write() palauttaa tuloksen, kun kirjoitetaan vähemmän tavuja kuin pyydettiin ja asetettu errno = EPIPE.

Kuka syyttää?

Kuvatussa tapauksessa vähän kaikkea. Virhe käsittelyssä cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) ei ole tarpeeton. Monissa tapauksissa on parempi erehtyä varovaisuuteen, vaikka se paljastaa äkilliset puutteet perinteiseen käyttäytymiseen suunnitellussa kattilassa.

Mitä tehdä?

väärä:

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

korjaa:

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

    Tässä komentotulkki käsittelee putken varhaisen sulkemisen huollettaessa sisäkkäistä liukuhihnaa (suluissa). Vastaavasti jos fortune ilmoittaa kirjoitusvirheestä suljetulle kanavalle tilassa ja sitten ulostuloon || echo "Ошибка" se ei pääse mihinkään, koska kanava on jo suljettu.

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

    Tässä on apuohjelma cat toimii vaimentimena, koska se jättää virheen huomioimatta EPIPE vetäytymisen yhteydessä. Tämä riittää nyt johtopäätökseksi fortune pieni (useita rivejä) ja mahtuu kanavapuskuriin (512 tavusta ≈64K, useimmissa käyttöjärjestelmissä ≥4K). Muuten ongelma saattaa palata.

Kuinka käsitellä oikein EPIPE ja muita tallennusvirheitä?

Ei ole yhtä oikeaa ratkaisua, mutta on olemassa yksinkertaisia ​​suosituksia:

  • EPIPE edellytetään täytyy käsitellä (ja näkyy poistumistilassa), kun tulostetaan tietoja, jotka edellyttävät eheyttä. Esimerkiksi arkistointi- tai varmuuskopiointiohjelmien käytön aikana.
  • EPIPE parempi jättää huomiotta tietoja ja apuviestejä näytettäessä. Esimerkiksi kun näytetään tietoja vaihtoehdoista --help tai --version.
  • Jos kehitettävää koodia voidaan käyttää liukuhihnassa ennen | head, Sitten EPIPE On parempi jättää huomiotta, muuten on parempi käsitellä ja heijastaa poistumistilaa.

Haluan käyttää tilaisuutta hyväkseni ja ilmaista kiitokseni joukkueille MCST и altlinux mahtavasta tuottavasta työstä. Päättäväisyytesi on hämmästyttävää!
Jatka samaan malliin Camarades, pysty kokouksia syksyllä!

Kiitos berez kirjoitus- ja virheiden korjaamiseen.
KDPV alkaen Georgi A.

Lähde: will.com

Lisää kommentti