Isa pang Heisenbug ang nakalampas sa buwaya

Isa pang Heisenbug ang nakalampas sa buwaya

$> set -o pipefail

$> fortune | head -1 > /dev/null && echo "ПовСзло!" || echo "Π’Ρ‹ ΠΏΡ€ΠΎΠΈΠ³Ρ€Π°Π»ΠΈ"
ПовСзло!

$> fortune | head -1 > /dev/null && echo "ПовСзло!" || echo "Π’Ρ‹ ΠΏΡ€ΠΎΠΈΠ³Ρ€Π°Π»ΠΈ"
Π’Ρ‹ ΠΏΡ€ΠΎΠΈΠ³Ρ€Π°Π»ΠΈ

Dito fortune kondisyonal na programa nang walang exit(rand()).

Maaari mo bang ipaliwanag? anong meron dito?

Lyrical-historical digression

Una kong nakilala ang Heisenbug na ito isang-kapat ng isang siglo ang nakalipas. Pagkatapos ay para sa gateway sa FaxNET kinakailangan na lumikha ng ilang mga utility sa pamamagitan ng pipa "naglalaro ng mga pamato" sa ilalim ng FreeBSD. Gaya ng inaasahan, itinuring ko ang aking sarili na isang advanced at medyo may karanasan na programmer. Samakatuwid, nilayon kong gawin ang lahat nang maingat at maingat hangga't maaari, na nagbibigay ng espesyal na pansin sa paghawak ng error...

Ang dati kong karanasan sa pagharap sa mga bug sa sendmail at uucp/uupc ay nagdagdag sa aking kasipagan sa "masusing paghawak ng error." Walang punto sa pagsisid sa mga detalye ng kuwentong iyon, ngunit nahirapan ako sa Heisenbug na ito sa loob ng dalawang linggo sa loob ng 10-14 na oras. Samakatuwid, ito ay naalala, at kahapon ang matandang kakilala na ito ay dumaan upang bisitahin muli.

Sagot ng TL;DR

Kagamitan head maaari isara ang channel mula sa fortune сразу pagkabasa niya sa unang linya. Kung fortune naglalabas ng higit sa isang linya, pagkatapos ay ang kaukulang tawag write() ay magbabalik ng isang error o ulat na mas kaunting mga byte ang output kaysa sa hiniling. Sa turn, nakasulat na may maingat na paghawak ng error fortune ay may karapatang ipakita ang sitwasyong ito sa katayuan ng paglabas nito. Pagkatapos ay dahil sa pag-install set -o pipefail magtatrabaho || echo "Π’Ρ‹ ΠΏΡ€ΠΎΠΈΠ³Ρ€Π°Π»ΠΈ".

Gayunpaman, head maaaring hindi maabot sa oras malapit na kanina fortune matatapos ang pag-output ng data. Pagkatapos ito ay gagana && echo "ПовСзло!".

Sa isa sa aking ngayon GNUMakefile may ganyan fragment:

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

Isinalin sa tao

Ito ay karaniwan dito para sa GNU Gumawa ΠΈ malakas na palo sa paraan ng compiler gamit ang opsyon --version ito ay nagtatanong kung sino siya, at kung ang opsyon ay hindi suportado, pagkatapos ay isang stub ay ipinasok "Pakigamit ang GCC o CLANG compatible compiler".

katulad boilerplate ay matatagpuan kahit saan. Ito ay lumitaw sa lugar na ito matagal na ang nakalipas at gumana nang perpekto sa lahat ng dako (Linux, Solaris, OSX, FreeBSD, WSL atbp.). Pero kahapon sa altlinux sa platform Elbrus 2000 (E2K) Napansin ko:

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

Sa totoo lang, hindi ko agad nakilala ang dati kong "kakilala." Bukod dito, ang proyekto ay nasubok nang maraming beses sa Elbrus at sa ilalim ng maraming iba't ibang mga pamamahagi, kabilang ang Alt. Sa iba't ibang compiler, bersyon ng GNU Make at bash. Samakatuwid, hindi ko nais na makita ang aking pagkakamali dito.

Kapag sinusubukang i-reproduce ang problema at/o unawain kung ano ang nangyayari, mas maraming kakaibang bagay ang nagsimulang mangyari.
Spell ng command line:

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

Paminsan-minsan ay gagawa ito ng dagdag na text, pagkatapos ay hindi... Kadalasan ang isa sa mga opsyon ay mananatili nang medyo matagal, ngunit kung sumundot ka nang mas mahaba, palagi mong makukuha ang pareho!

Siyempre, strace ating lahat! At sa pag-type ng isang strace tirade, ngunit walang oras upang pindutin ang Enter, nakilala ko ang aking matandang kaibigan na si Mr. Heisenbug, at ang mga developer compiler ang aking sarili 25 taon na ang nakakaraan, Nostalgia… At nagpasya akong maging malungkot at isulat ang tala na ito πŸ˜‰

Sa pamamagitan ng paraan, tulad ng anumang paggalang sa sarili Heisenbug, sa ilalim strace mas pinipiling hindi magparami.

Kaya ano ang nangyayari?

  • Kagamitan head ay may karapatan (o sa halip, napipilitan pa nga) na isara ang channel na binabasa sa sandaling mabasa nito ang hiniling na bilang ng mga linya.
  • Ang data-generating program-writer (sa kasong ito cc) maaari mag-print ng maraming linya at libre gawin ito sa pamamagitan ng maraming tawag write().
  • Kung magkakaroon ng oras ang mambabasa na isara ang channel sa kanyang panig bago matapos ang pag-record sa panig ng manunulat, pagkatapos ay makakatanggap ng error ang manunulat.
  • Programa ng manunulat ay may tama parehong binabalewala ang channel write error at ipinapakita ito sa iyong completion code.
  • Dahil sa pag-install set -o pipefail ang code ng pagkumpleto ng pipeline ay magiging non-zero (mali) kung ang resulta ay hindi zero mula sa hindi bababa sa isang elemento, at pagkatapos ay gagana ito || echo "Please use GCC or CLANG compatible compiler".

Maaaring may mga pagkakaiba-iba depende sa kung paano gumagana ang programa ng manunulat sa mga signal. Halimbawa, ang programa ay maaaring magwakas nang hindi normal (na may awtomatikong pagbuo ng isang hindi zero/error termination status), o write() ibabalik ang resulta ng pagsulat ng mas kaunting mga byte kaysa sa hiniling at itinakda errno = EPIPE.

Sino ang sisihin?

Sa inilarawang kaso kaunti sa lahat. Error sa paghawak cc (lcc:1.23.20:Sepβ€”4-2019:e2k-v3-linux) ay hindi kalabisan. Sa maraming mga kaso, mas mabuting magkamali sa panig ng pag-iingat, bagama't ito ay naglalantad ng mga biglaang pagkukulang sa isang boilerplate na idinisenyo para sa tradisyonal na pag-uugali.

Ano ang dapat gawin?

Maling:

fortune | head -1 && echo "ПовСзло, Π½ΠΎ Π²Ρ‹ рискуСтС!" || echo "WTF?"

Tamang:

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

    Dito, ang maagang pagsasara ng pipe ay hahawakan ng command interpreter kapag sineserbisyuhan ang nested pipeline ("sa loob" ng mga panaklong). Alinsunod dito, kung fortune ay mag-uulat ng error sa pagsulat sa isang saradong channel sa status, pagkatapos ay ang output || echo "Ошибка" hindi ito makakarating kahit saan, dahil sarado na ang channel.

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

    Narito ang utility cat gumaganap bilang isang damper dahil binabalewala nito ang error EPIPE sa pag-withdraw. Ito ay sapat na para sa ngayon konklusyon fortune maliit (ilang linya) at akma sa buffer ng channel (mula 512 bytes hanggang β‰ˆ64K, sa karamihan ng OS β‰₯4K). Kung hindi, maaaring bumalik ang problema.

Paano magproseso ng tama EPIPE at iba pang mga error sa pag-record?

Walang iisang tamang solusyon, ngunit may mga simpleng rekomendasyon:

  • EPIPE kinakailangan dapat iproseso (at makikita sa exit status) kapag naglalabas ng data na nangangailangan ng integridad. Halimbawa, sa panahon ng pagpapatakbo ng mga archiver o backup na kagamitan.
  • EPIPE mas mabuting huwag pansinin kapag nagpapakita ng impormasyon at mga pantulong na mensahe. Halimbawa, kapag nagpapakita ng impormasyon sa mga opsyon --help o --version.
  • Kung ang code na binuo ay maaaring gamitin sa isang pipeline bago | head, Pagkatapos EPIPE Ito ay mas mahusay na huwag pansinin, kung hindi, ito ay mas mahusay na iproseso at sumasalamin sa exit status.

Gusto kong samantalahin ang pagkakataong ito upang ipahayag ang aking pasasalamat sa mga koponan MCST ΠΈ altlinux para sa mahusay na produktibong trabaho. Kahanga-hanga ang iyong determinasyon!
Keep it up Camarades, up mga pagpupulong sa taglagas!

salamat berez para sa pagwawasto ng mga typo at error.
KDPV mula sa Georgy A.

Pinagmulan: www.habr.com

Magdagdag ng komento