又一隻海森蟲超越了鱷魚

又一隻海森蟲超越了鱷魚

$> set -o pipefail

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

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

這裡 fortune 條件程序無 exit(rand()).

你可以解釋嗎? 這裡出了什麼問題?

抒情歷史題外話

我第一次認識這個 Heisenbug 是在二十五年前。 然後,對於 FaxNET 中的網關,需要透過以下方式建立多個實用程序 管道 FreeBSD 下的「下棋」。 正如預期的那樣,我認為自己是一名高級且經驗豐富的程式設計師。 因此,我打算盡可能小心謹慎地做每件事,特別注意錯誤處理......

我之前處理 sendmail 和 uucp/uupc 中的錯誤的經驗增加了我在「徹底的錯誤處理」方面的努力。 深入研究這個故事的細節是沒有意義的,但我在這個 Heisenbug 上掙扎了兩週 10-14 個小時。 於是,就記住了,昨天這位老熟人又來拜訪了。

TL;DR 答案

效用 head 可以 關閉通道 fortune 立刻 當他讀到第一行。 如果 fortune 輸出多一行,則對應調用 write() 將傳回錯誤或報告輸出的位元組數少於請求的位元組數。 反過來,用仔細的錯誤處理來寫 fortune 有權在其退出狀態中反映這種情況。 然後由於安裝 set -o pipefail 將工作 || echo "Вы проиграли".

但是, head 可能無法及時趕到 之前關閉 fortune 將完成數據輸出。 然後就可以了 && echo "Повезло!".

在我的今天之一 GNUMakefile 有一個 分段:

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

翻譯成人類

這裡常見的是 GNU 製造 и 打壞 以編譯器的方式使用選項 --version 它詢問他是誰,如果不支援該選項,則會插入一個存根 “請使用 GCC 或 CLANG 相容編譯器”.

樣板 隨處可見。 它很久以前就出現在這個地方,並且在任何地方都能完美運作(Linux、Solaris、OSX、FreeBSD、 WSL ETC.)。 但昨天在 替代Linux 在平台上 厄爾布魯士峰 2000 (E2K) 我注意到:

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

坦白說,我並沒有立即認出我的老「熟人」。 此外,該專案已經在 Elbrus 和許多不同的發行版(包括 Alt)下進行了多次測試。 具有各種編譯器、GNU Make 和 bash 版本。 因此,我不想在這裡看到我的錯誤。

當試圖重現問題和/或了解發生了什麼時,更多奇怪的事情開始發生。
命令列拼字:

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

它時不時地會產生額外的文本,然後就不會了……通常其中一個選項會保留相當長的時間,但如果你戳得更久,你總是會得到兩個!

當然 strace 我們的一切! 在輸入了 strace 長篇大論,但沒有時間按 Enter 鍵時,我認出了我的老朋友 Heisenbug 先生和開發人員 編譯器 25年前的我自己, 懷舊之情… 我決定悲傷地寫下這張紙條😉

順便說一句,就像任何有自尊心的人一樣 海森巴格, 在下面 strace 寧願不複製。

發生什麼了?

  • 效用 head 一旦讀取了請求的行數,就有權利(或更確切地說,甚至被迫)關閉正在讀取的通道。
  • 資料生成程式編寫者(在本例中 cc) 可以 列印多行並 自由的 透過多次調用來做到這一點 write().
  • 如果 在寫入端記錄結束之前,讀取端有時間關閉自己端的通道,然後寫入端會收到錯誤。
  • 作家計劃 有權利 兩者都會忽略通道寫入錯誤並將其反映在完成程式碼中。
  • 由於安裝原因 set -o pipefail 如果至少一個元素的結果非零,則管道完成程式碼將非零(錯誤),然後它將起作用 || echo "Please use GCC or CLANG compatible compiler".

根據編寫程式如何處理訊號,可能會有所不同。 例如,程式可能異常終止(自動產生非零/錯誤終止狀態),或者 write() 將返回寫入的位元組數少於請求和設定的位元組數的結果 errno = EPIPE.

誰應該受到責備?

在所描述的情況下 一切都一點點。 錯誤處理在 cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) 不是 多餘的。 在許多情況下,最好還是謹慎行事,儘管這確實會暴露出為傳統行為設計的樣板檔案中突然存在的缺陷。

怎麼辦呢?

錯誤:

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

更正:

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

    在這裡,在服務嵌套管道(括號“內”)時,命令解釋器將處理管道的提前關閉。 因此,如果 fortune 會在status中報寫入關閉通道錯誤,然後輸出 || echo "Ошибка" 它不會到達任何地方,因為通道已經關閉。

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

    這是實用程式 cat 充當阻尼器,因為它忽略了誤差 EPIPE 撤回後。 現在就可以下結論了 fortune 小(幾行)並且適合通道緩衝區(從 512 位元組到 ≈64K,在大多數作業系統中≥4K)。 否則問題可能會再次出現。

如何正確處理 EPIPE 以及其他錄音錯誤?

沒有單一正確的解決方案,但有一些簡單的建議:

  • EPIPE 需要 必須處理 (並反映在退出狀態中)當輸出需要完整性的資料時。 例如,在歸檔程式或備份實用程式的作業期間。
  • EPIPE 最好忽略 顯示訊息和輔助訊息時。 例如,當顯示選項資訊時 --help--version.
  • 如果正在開發的程式碼之前可以在管道中使用 | head,然後 EPIPE 最好忽略,否則最好處理並反映在退出狀態中。

我想藉此機會向團隊表示感謝 MCST и 替代Linux 進行富有成效的工作。 你的決心真是驚人!
繼續努力 同志們,加油 秋季會議!

謝謝 別雷斯 用於糾正拼字錯誤和錯誤。
KDPV 來自 喬治·A.

來源: www.habr.com

添加評論