Veel üks Heisenbug mööda krokodilli

Veel üks Heisenbug mööda krokodilli

$> set -o pipefail

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

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

see on fortune tingimuslik programm ilma exit(rand()).

Kas saate selgitada? mis siin viga on?

Lüürilis-ajalooline kõrvalepõige

Esimest korda sain selle Heisenbugiga tuttavaks veerand sajandit tagasi. Seejärel tuli FaxNETi lüüsi jaoks luua mitu utiliiti torud "kabe mängimine" FreeBSD all. Ootuspäraselt pidasin end edasijõudnud ja küllaltki kogenud programmeerijaks. Seetõttu kavatsesin teha kõike võimalikult hoolikalt ja hoolikalt, pöörates erilist tähelepanu vigade käsitlemisele...

Minu varasem kogemus Sendmaili ja uucp/uupc vigadega tegelemisel suurendas minu hoolsust "põhjaliku veakäsitluse" alal. Selle loo detailidesse pole mõtet sukelduda, aga selle Heisenbugiga vaevlesin kaks nädalat 10-14 tundi. Seetõttu jäi see meelde ja eile astus see vana tuttav jälle külla.

TL;DR Vastus

Utiliit head võib sulgege kanal alates fortune korraga niipea, kui ta loeb esimest rida. Kui fortune väljastab rohkem kui ühe rea, seejärel vastava kõne write() tagastab veateate või teatab, et väljastatakse nõutust vähem baite. Omakorda hoolika veakäsitlusega kirjutatud fortune on õigus seda olukorda oma väljumisolekus kajastada. Siis paigalduse tõttu set -o pipefail töötab || echo "Вы проиграли".

Kuid head ei pruugi õigeks ajaks jõuda enne kinni fortune lõpetab andmete väljastamise. Siis see toimib && echo "Повезло!".

Ühes minu tänases päevas GNUMakefile selline on olemas fragment:

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

Inimeseks tõlgitud

See on siin tavaline GNU kaubamärk и sisse lööma kompilaatori viisil, kasutades valikut --version see küsib, kes ta on ja kui seda valikut ei toetata, siis sisestatakse tünn "Palun kasutage GCC või CLANG-iga ühilduvat kompilaatorit".

nagu katlakivi võib leida kõikjal. See ilmus selles kohas juba ammu ja töötas suurepäraselt kõikjal (Linux, Solaris, OSX, FreeBSD, WSL jne.). Aga eile sisse altlinux platvormil Elbrus 2000 (E2K) Ma märkasin:

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

Ausalt öeldes ei tundnud ma oma vana "tuttavat" kohe ära. Lisaks on projekti juba mitu korda testitud Elbrusel ja paljude erinevate distributsioonide all, sealhulgas Alt. Erinevate kompilaatorite, GNU Make ja bashi versioonidega. Seetõttu ei tahtnud ma siin oma viga näha.

Püüdes probleemi reprodutseerida ja/või aru saada, mis toimub, hakkas juhtuma kummalisemaid asju.
Käsurea loits:

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

Aeg-ajalt toodaks lisateksti, siis mitte... Tihti jäi üks variant päris pikaks ajaks kinni, aga pikemalt torkides sai alati mõlemad!

Muidugi, strace meie kõik! Ja olles tippinud tiraadi, kuid mul polnud aega sisestusklahvi vajutada, tundsin ära oma vana sõbra härra Heisenbugi ja arendajad koostaja ma ise 25 aastat tagasi, Nostalgia… Ja otsustasin olla kurb ja selle märkme kirjutada 😉

Muide, nagu iga endast lugupidav Heisenbug, all strace eelistab mitte paljuneda.

Mis siis toimub?

  • Utiliit head on õigus (õigemini isegi sunnitud) sulgeda loetav kanal kohe, kui see loeb nõutud arvu ridu.
  • Andmeid genereeriv programmikirjutaja (antud juhul cc) võib printida mitu rida ja tasuta tehke seda mitme kõne kaudu write().
  • kui lugejal on aega enne kirjutajapoolse salvestuse lõppu kanal enda poolelt sulgeda, siis saab kirjutaja veateate.
  • Kirjaniku programm õigus mõlemad ignoreerivad kanali kirjutamisviga ja kajastavad seda teie lõpetamiskoodis.
  • Paigaldamise tõttu set -o pipefail konveieri lõpu kood on nullist erinev (vigane), kui tulemus on nullist erinev vähemalt ühest elemendist ja siis see töötab || echo "Please use GCC or CLANG compatible compiler".

Sõltuvalt sellest, kuidas kirjutamisprogramm signaalidega töötab, võib esineda erinevusi. Näiteks võib programm ebanormaalselt lõppeda (null-/viga-lõpetamisoleku automaatse genereerimisega) või write() tagastab tulemuse, kui kirjutatakse vähem baite, kui nõutud ja seatud errno = EPIPE.

Kes on süüdi?

Kirjeldatud juhul natuke kõike. Viga sissetöötamisel cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) ei ole üleliigne. Paljudel juhtudel on parem eksida ettevaatusega, kuigi see paljastab tavapärase käitumise jaoks loodud katlaplaadi äkilised vead.

Mida teha?

Vale:

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

Õigesti:

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

    Siin käsitleb toru varajast sulgemist käskude tõlk pesastatud konveieri teenindamisel (sulgude sees). Vastavalt sellele, kui fortune teatab kirjutamisveast olekus suletud kanalile, seejärel väljundile || echo "Ошибка" see ei jõua kuhugi, kuna kanal on juba suletud.

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

    Siin on utiliit cat toimib siibrina, kuna eirab viga EPIPE taganemisel. Sellest piisab praeguseks järelduseks fortune väike (mitu rida) ja mahub kanalipuhvrisse (512 baidist ≈64K, enamikus OS-ides ≥4K). Vastasel juhul võib probleem taastuda.

Kuidas õigesti töödelda EPIPE ja muid salvestusvigu?

Pole olemas ühte õiget lahendust, kuid on lihtsad soovitused:

  • EPIPE nõutav tuleb töödelda (ja kajastub väljumisolekus) terviklikkust nõudvate andmete väljastamisel. Näiteks arhiivide või varundusutiliitide töötamise ajal.
  • EPIPE parem ignoreerida teabe ja abiteadete kuvamisel. Näiteks valikute kohta teabe kuvamisel --help või --version.
  • Kui arendatavat koodi saab enne konveier kasutada | headsiis EPIPE Parem on ignoreerida, vastasel juhul on parem töödelda ja kajastada väljumisolekut.

Kasutan võimalust ja tänan meeskondi MCST и altlinux suure tulemusliku töö eest. Teie otsusekindlus on hämmastav!
Jätkake, Camarad, jätka koosolekud sügisel!

Tänan berez kirja- ja vigade parandamiseks.
KDPV alates Georgi A.

Allikas: www.habr.com

Lisa kommentaar