Još jedan Heisenbug pored krokodila

Još jedan Heisenbug pored krokodila

$> set -o pipefail

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

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

to je fortune uslovni program bez exit(rand()).

Možeš li objasniti? šta nije u redu?

Lirsko-istorijska digresija

Prvi put sam se upoznao sa ovim Heisenbugom pre četvrt veka. Zatim je za gateway u FaxNET-u bilo potrebno kreirati nekoliko uslužnih programa putem cijevi "igranje dame" pod FreeBSD-om. Očekivano, smatrao sam se naprednim i prilično iskusnim programerom. Stoga sam namjeravao sve učiniti što je moguće pažljivije i pažljivije, obraćajući posebnu pažnju na rješavanje grešaka...

Moje prethodno iskustvo bavljenja greškama u sendmailu i uucp/uupc dodalo je mojoj marljivosti u „temeljnom rukovanju greškama“. Nema smisla uranjati u detalje te priče, ali ja sam se sa ovim Heisenbugom mučio dvije sedmice po 10-14 sati. Stoga je ostalo upamćeno, a juče je ovaj stari znanac ponovo svratio u posjetu.

TL;DR odgovor

Utility head moći zatvorite kanal iz fortune сразу čim pročita prvi red. Ako fortune izlazi više od jedne linije, zatim odgovarajući poziv write() će vratiti grešku ili prijaviti da je izlaz manje bajtova od traženog. Zauzvrat, napisano uz pažljivo rukovanje greškama fortune ima pravo da ovu situaciju odrazi u svom izlaznom statusu. Zatim zbog instalacije set -o pipefail će raditi || echo "Вы проиграли".

Međutim, head možda neće stići na vrijeme zatvori prije fortune će završiti sa izlazom podataka. Onda će raditi && echo "Повезло!".

U jednom od mojih današnjih GNUMakefile postoji takav fragment:

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

Prevedeno na ljudski

Ovde je uobičajeno GNU Make и bash na način kompajlera koristeći opciju --version pita ko je on, a ako opcija nije podržana, onda se ubacuje stub "Molimo koristite GCC ili CLANG kompatibilan kompajler".

poput bojler može se naći bilo gdje. Pojavio se na ovom mjestu davno i savršeno je radio svuda (Linux, Solaris, OSX, FreeBSD, WSL itd.). Ali juče u altlinux na platformi Elbrus 2000 (E2K) Primjetio sam:

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

Iskreno, nisam odmah prepoznao svog starog "poznanika". Štaviše, projekat je već testiran mnogo puta na Elbrusu i pod mnogo različitih distribucija, uključujući Alt. Sa raznim kompajlerima, verzijama GNU Make i bash. Stoga, nisam želio ovdje vidjeti svoju grešku.

Prilikom pokušaja da se reproducira problem i/ili shvati šta se dešava, počele su se dešavati još čudnije stvari.
Čarolija komandne linije:

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

S vremena na vrijeme proizveo bi dodatni tekst, a onda ne... Često bi se jedna od opcija držala prilično dugo, ali ako bi dulje pickali, uvijek ste dobili obje!

Naravno, strace naše sve! I pošto sam otkucao strace tiradu, ali nisam imao vremena da pritisnem Enter, prepoznao sam svog starog prijatelja gospodina Heisenbuga i programere kompajler ja prije 25 godina, nostalgija… I odlučila sam da budem tužna i da napišem ovu poruku 😉

Usput, kao i svako samopoštovanje Heisenbug, ispod strace preferira da se ne razmnožava.

Pa šta se dešava?

  • Utility head ima pravo (tačnije, čak je prinuđen) da zatvori kanal koji se čita čim pročita traženi broj redova.
  • Program za generiranje podataka (u ovom slučaju cc) moći ispisati više redova i besplatno uradite to putem više poziva write().
  • ako čitalac će imati vremena da zatvori kanal na svojoj strani prije kraja snimanja na strani pisca, tada će pisac dobiti grešku.
  • Program za pisanje ima pravo oba ignoriraju grešku pisanja kanala i odražavaju je u vašem kodu za završetak.
  • Zbog instalacije set -o pipefail kod završetka cjevovoda će biti različit od nule (pogrešan) ako je rezultat različit od nule iz barem jednog elementa, i tada će raditi || echo "Please use GCC or CLANG compatible compiler".

Mogu postojati varijacije ovisno o tome kako program za pisanje radi sa signalima. Na primjer, program se može nenormalno prekinuti (sa automatskim generiranjem statusa završetka koji nije nula/greška), ili write() će vratiti rezultat pisanja manjeg broja bajtova od traženog i postavljenog errno = EPIPE.

Ko je kriv?

U opisanom slučaju od svega po malo. Greška pri rukovanju cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nije suvišan. U mnogim slučajevima je bolje pogriješiti na strani opreza, iako to otkriva iznenadne nedostatke u šablonu dizajniranom za tradicionalno ponašanje.

Što da radim?

Pogrešno:

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

Tačno:

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

    Ovdje će rano zatvaranje cijevi biti obrađeno od strane interpretatora komande prilikom servisiranja ugniježđenog cjevovoda ("unutar" zagrada). Shodno tome, ako fortune će prijaviti grešku u pisanoj formi zatvorenom kanalu u statusu, zatim izlaz || echo "Ошибка" neće stići nikuda, pošto je kanal već zatvoren.

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

    Evo uslužnog programa cat djeluje kao amortizer jer ignorira grešku EPIPE po povlačenju. Ovo je dovoljno za sada zaključak fortune mali (nekoliko linija) i stane u bafer kanala (od 512 bajtova do ≈64K, u većini OS ≥4K). U suprotnom se problem može vratiti.

Kako postupati EPIPE i druge greške u snimanju?

Ne postoji jedno pravo rješenje, ali postoje jednostavne preporuke:

  • EPIPE potrebno moraju biti obrađene (i odražava se u izlaznom statusu) pri izlasku podataka koji zahtijevaju integritet. Na primjer, tokom rada arhivatora ili uslužnih programa za pravljenje rezervnih kopija.
  • EPIPE bolje ignorisati prilikom prikaza informacija i pomoćnih poruka. Na primjer, kada se prikazuju informacije o opcijama --help ili --version.
  • Ako se kod koji se razvija može koristiti u cjevovodu prije | head, onda EPIPE Bolje je zanemariti, inače je bolje obraditi i odraziti u izlaznom statusu.

Iskoristio bih priliku da izrazim zahvalnost timovima MCST и altlinux za odličan produktivan rad. Vaša odlučnost je neverovatna!
Samo tako, Kamaradi, gore sastanke na jesen!

Spasibo berez za ispravljanje grešaka u kucanju.
KDPV iz Georgij A.

izvor: www.habr.com

Dodajte komentar