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 "Вы проиграли"
Вы проиграли

Ovdje fortune uvjetni program bez exit(rand()).

Možeš li objasniti? što ovdje nije u redu?

Lirsko-povijesna digresija

S tim sam se Heisenbugom prvi put upoznao prije četvrt stoljeća. Tada je za gateway u FaxNET-u bilo potrebno stvoriti nekoliko pomoćnih programa putem cijevi "igranje dame" pod FreeBSD-om. Očekivano, smatrao sam se naprednim i prilično iskusnim programerom. Stoga sam namjeravao učiniti sve što je moguće pažljivije i pažljivije, obraćajući posebnu pozornost na rukovanje greškama...

Moje prethodno iskustvo rješavanja grešaka u sendmailu i uucp/uupc-u doprinijelo je mojoj marljivosti u "temeljitom rješavanju grešaka". Nema smisla ulaziti u detalje te priče, ali s ovim Heisenbugom sam se mučio dva tjedna po 10-14 sati. Stoga je ostalo zapamćeno, a jučer je ovaj stari znanac ponovno svratio u goste.

TL;DR Odgovor

Korisnost head može zatvoriti kanal iz fortune сразу čim pročita prvi redak. Ako fortune ispisuje više od jedne linije, zatim odgovarajući poziv write() vratit će pogrešku ili izvijestiti da je izlaz manje bajtova od traženog. Zauzvrat, napisano uz pažljivo rukovanje pogreškama fortune ima pravo odražavati ovu situaciju u svom izlaznom statusu. Zatim zbog ugradnje set -o pipefail će raditi || echo "Вы проиграли".

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

U jednoj 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

Ovdje je uobičajeno za GNU Make и udariti na način prevoditelja koristeći opciju --version pita se tko je on, a ako opcija nije podržana, ubacuje se zaglavak "Molimo koristite GCC ili CLANG kompatibilni kompajler".

kao predložak može se naći bilo gdje. Na ovom mjestu se pojavio davno i svugdje je savršeno radio (Linux, Solaris, OSX, FreeBSD, WSL itd.). Ali jučer 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 govoreći, nisam odmah prepoznao svog starog "poznanika". Štoviše, projekt je već testiran mnogo puta na Elbrusu i pod puno različitih distribucija, uključujući Alt. S raznim kompajlerima, verzijama GNU Make i bash. Stoga nisam želio vidjeti svoju grešku ovdje.

Prilikom pokušaja reproduciranja problema i/ili razumijevanja što se događa, počele su se događati još čudne stvari.
Čarolija naredbenog retka:

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

Tu i tamo proizveo bi dodatni tekst, pa ne... Često bi se jedna od opcija zadržala dosta dugo, ali ako ste dulje bockali, uvijek ste dobili obje!

Naravno, strace naše sve! I tako, nakon što sam upisao strace tiradu, ali nisam imao vremena pritisnuti Enter, prepoznao sam svog starog prijatelja gospodina Heisenbuga i programere sastavljač ja prije 25 godina, Nostalgija… I odlučila sam biti tužna i napisati ovu poruku 😉

Usput, kao i svaki koji poštuje sebe Heisenbug, pod, ispod strace radije se ne razmnožava.

I što ima?

  • Korisnost head ima pravo (ili bolje rečeno, čak je prisiljen) zatvoriti kanal koji se čita čim pročita traženi broj redaka.
  • Autor programa za generiranje podataka (u ovom slučaju cc) može ispisati više redaka i besplatno učiniti to kroz više poziva write().
  • Ako čitatelj će imati vremena zatvoriti kanal na svojoj strani prije kraja snimanja na strani pisca, tada će pisac primiti pogrešku.
  • Program Writer pravo na oba zanemaruju pogrešku pisanja kanala i odražavaju je u vašem kodu za dovršetak.
  • Zbog ugradnje set -o pipefail kod završetka cjevovoda bit će 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 prekinuti neuobičajeno (s automatskim generiranjem statusa prekida koji nije nula/pogreška), ili write() će vratiti rezultat zapisivanja manjeg broja bajtova nego što je traženo i postavljeno errno = EPIPE.

Tko je kriv?

U opisanom slučaju od svega po malo. Obrada pogreške u cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nije blagoglagoljiv. U mnogim slučajevima bolje je pogriješiti na strani opreza, iako to otkriva iznenadne nedostatke u šabloni dizajniranoj za tradicionalno ponašanje.

Što učiniti?

Pogrešno:

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

ispraviti:

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

    Ovdje će ranim zatvaranjem cijevi upravljati tumač naredbi prilikom servisiranja ugniježđenog cjevovoda ("unutar" zagrada). Prema tome, ako fortune prijavit će pogrešku u pisanom obliku zatvorenom kanalu u statusu, zatim izlaz || echo "Ошибка" neće nikamo stići, jer je kanal već zatvoren.

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

    Evo korisnosti cat djeluje kao prigušivač jer ignorira pogrešku EPIPE pri povlačenju. Ovo je dovoljno za sada zaključak fortune mali (nekoliko redaka) i stane u međuspremnik kanala (od 512 bajtova do ≈64K, u većini OS-a ≥4K). Inače se problem može vratiti.

Kako pravilno obraditi EPIPE i druge greške snimanja?

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

  • EPIPE potreban mora se obraditi (i odražava se u statusu izlaza) prilikom ispisa podataka koji zahtijevaju cjelovitost. Na primjer, tijekom rada arhivara ili uslužnih programa za sigurnosno kopiranje.
  • EPIPE bolje ignorirati 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 | headtada EPIPE Bolje je ignorirati, inače je bolje obraditi i odražavati u statusu izlaza.

Želio bih iskoristiti ovu priliku da izrazim svoju zahvalnost timovima MCST и altlinux za izvrstan produktivan rad. Vaša je odlučnost nevjerojatna!
Samo tako nastavi Camarades, gore sastanci u jesen!

Hvala berez za ispravljanje tipfelera i pogrešaka.
KDPV iz Georgij A.

Izvor: www.habr.com

Dodajte komentar