Daha bir Heisenbug timsahın yanından keçdi

Daha bir Heisenbug timsahın yanından keçdi

$> set -o pipefail

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

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

Burada fortune şərti proqram olmadan exit(rand()).

İzah edə bilərsən? burda nə olub?

Lirik-tarixi təxribat

Mən bu Heisenbuqla ilk dəfə dörddə bir əsr əvvəl tanış olmuşam. Sonra FaxNET-də şlüz üçün bir neçə yardım proqramı yaratmaq lazım idi borular FreeBSD altında "dama oynamaq". Gözlənildiyi kimi, özümü qabaqcıl və kifayət qədər təcrübəli proqramçı hesab edirdim. Buna görə də, səhvlərin həllinə xüsusi diqqət yetirərək, hər şeyi mümkün qədər diqqətlə və diqqətlə etmək niyyətində idim...

Sendmail və uucp/uupc-də səhvlərlə məşğul olmaq üzrə əvvəlki təcrübəm “xətaların hərtərəfli həllində” çalışqanlığıma əlavə edildi. O hekayənin təfərrüatlarına dalmağın mənası yoxdur, amma mən bu Heisenbug ilə iki həftə 10-14 saat mübarizə apardım. Ona görə də yada düşdü və dünən bu köhnə tanışımız yenə qonaq getdi.

TL; DR Cavab

Kommunal head can kanalı bağlayın fortune сразу birinci sətri oxuyan kimi. Əgər fortune birdən çox sətir, sonra müvafiq zəngi çıxarır write() xəta qaytaracaq və ya tələb olunandan daha az bayt çıxdığını bildirəcək. Öz növbəsində, ehtiyatlı səhv rəftar ilə yazılmışdır fortune bu vəziyyəti öz çıxış statusunda əks etdirmək hüququna malikdir. Sonra quraşdırma səbəbiylə set -o pipefail işləyəcək || echo "Вы проиграли".

Lakin, head vaxtında yetişməyə bilər əvvəl yaxın fortune məlumatların çıxarılmasını başa çatdıracaq. Sonra işləyəcək && echo "Повезло!".

Bu günlərimin birində GNUMakefile belə var fraqment:

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

İnsan dilinə tərcümə edilmişdir

Bunun üçün burada adi haldır GNU olun и bash seçimindən istifadə edərək kompilyator üsulu ilə --version onun kim olduğunu soruşur və seçim dəstəklənmirsə, o zaman qaralama daxil edilir "Zəhmət olmasa GCC və ya CLANG uyğun kompilyatordan istifadə edin".

kimi qazanxana hər yerdə tapmaq olar. Bu yerdə çoxdan yaranıb və hər yerdə mükəmməl işləyirdi (Linux, Solaris, OSX, FreeBSD, WSL və s.). Amma dünən altlinux platformada Elbrus 2000 (E2K) Diqqət yetirdim:

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

Açığı, köhnə “tanışımı” dərhal tanımadım. Üstəlik, layihə artıq dəfələrlə Elbrus-da və Alt da daxil olmaqla bir çox müxtəlif paylamalar altında sınaqdan keçirilmişdir. Müxtəlif tərtibçilər, GNU Make və bash versiyaları ilə. Ona görə də səhvimi burada görmək istəmədim.

Problemi təkrar etməyə və/və ya nə baş verdiyini anlamağa çalışarkən daha qəribə şeylər baş verməyə başladı.
Komanda xətti sehri:

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

Hərdən və sonra o, əlavə mətn yaradırdı, sonra isə yox... Çox vaxt variantlardan biri kifayət qədər uzun müddət qalırdı, lakin siz daha uzun sürsəniz, həmişə hər ikisini əldə edirsiniz!

Əlbəttə ki, strace bizim hər şeyimiz! Mən bir tirad yazıb, lakin Enter düyməsini basmağa vaxt tapmadan köhnə dostum cənab Heisenbugu və tərtibatçıları tanıdım. tərtibçi 25 il əvvəl özüm Nostalji… Mən də kədərlənib bu qeydi yazmağa qərar verdim 😉

Yeri gəlmişkən, hər hansı bir özünə hörmət edən kimi Heisenbug, altında strace çoxalmamağa üstünlük verir.

Bəs nə baş verir?

  • Kommunal head tələb olunan sayda sətir oxuyan kimi oxunan kanalı bağlamaq hüququna malikdir (daha doğrusu, hətta məcburdur).
  • Məlumat yaradan proqram müəllifi (bu halda cc) can çoxlu sətirləri çap edin və pulsuz bunu çoxlu zənglər vasitəsilə edin write().
  • Əgər oxucunun yazıçı tərəfində qeyd bitənə qədər öz tərəfindəki kanalı bağlamağa vaxtı olacaq, sonra yazıçı xəta alacaq.
  • Yazıçı proqramı hüququ var həm kanal yazma xətasına məhəl qoymur, həm də onu tamamlama kodunuzda əks etdirir.
  • Quraşdırmaya görə set -o pipefail nəticə ən azı bir elementdən sıfırdan fərqli olarsa, boru kəmərinin tamamlama kodu sıfırdan fərqli (səhv) olacaq və sonra işləyəcək || echo "Please use GCC or CLANG compatible compiler".

Yazıçı proqramının siqnallarla necə işləməsindən asılı olaraq dəyişikliklər ola bilər. Məsələn, proqram anormal şəkildə dayandırıla bilər (sıfır olmayan/səhv xitam statusunun avtomatik yaradılması ilə) və ya write() tələb olunandan və təyin ediləndən daha az bayt yazmanın nəticəsini qaytaracaq errno = EPIPE.

Kim günahlandırır?

Təsvir edilən vəziyyətdə hər şeydən bir az. İdarəetmə xətası cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) deyil lazımsız. Bir çox hallarda ehtiyatlı olmaqda səhv etmək daha yaxşıdır, baxmayaraq ki, bu, ənənəvi davranış üçün nəzərdə tutulmuş qazanxanada qəfil qüsurları üzə çıxarır.

Bəs nə etməli?

Səhv:

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

Düzgün:

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

    Burada borunun erkən bağlanması, iç-içə boru kəmərinə xidmət göstərərkən (“mötərizədə”) əmr tərcüməçisi tərəfindən idarə olunacaq. Müvafiq olaraq, əgər fortune statusunda qapalı bir kanala yazılan səhvi bildirəcək, daha sonra çıxış || echo "Ошибка" kanal artıq bağlandığı üçün heç yerə çatmayacaq.

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

    Budur kommunal cat xətaya məhəl qoymadığı üçün damper rolunu oynayır EPIPE geri çəkildikdən sonra. İndiki nəticə üçün bu kifayətdir fortune kiçik (bir neçə sətir) və kanal buferinə uyğundur (512 baytdan ≈64K, əksər OS-lərdə ≥4K). Əks halda problem geri qayıda bilər.

Necə idarə etmək EPIPE və digər qeyd səhvləri?

Tək düzgün həll yoxdur, lakin sadə tövsiyələr var:

  • EPIPE tələb olunur emal edilməlidir bütövlük tələb edən verilənləri çıxararkən (və çıxış statusunda əks olunur). Məsələn, arxivçilərin və ya ehtiyat kommunalların işləməsi zamanı.
  • EPIPE göz ardı etmək daha yaxşıdır məlumat və köməkçi mesajlar göstərildikdə. Məsələn, seçimlər haqqında məlumat göstərərkən --help və ya --version.
  • Əgər inkişaf etdirilən kod daha əvvəl bir boru xəttində istifadə edilə bilər | head, Sonra EPIPE Göz ardı etmək daha yaxşıdır, əks halda çıxış statusunda emal etmək və əks etdirmək daha yaxşıdır.

Fürsətdən istifadə edib komandalara öz təşəkkürümü bildirmək istəyirəm MCST и altlinux böyük məhsuldar iş üçün. Qətiyyətiniz heyrətamizdir!
Davam edin Camarades, ayağa payızda görüşlər!

Təşəkkür berez səhvləri və səhvləri düzəltmək üçün.
KDPV-dən Georgi A.

Mənbə: www.habr.com

Добавить комментарий