一個值得大家觀摩學習的除錯過程
最近工作上踩到這個 MaaS 的 bug,這個 bug 結合了號稱是最難除錯的兩個特質 1) 隨機出現、複製率低 2) 網路問題、環境變因多且難以一一固定。不過裡面強者我同事 dann 在 bug 中展示了他如何(快速地) nail down 原因,非常精彩;下面我分別聊聊這整個過程很值得大家觀摩學習的面向。
清楚的解決脈絡
整個過程雖然一開始幾乎沒什麼頭緒,但經過簡單觀察後,就很快地決定採用之前提到的精確具體的假說與實證策略:wget 並不是對同一個目的地每次能有效取得檔案,認為理解這個原因的話就可以找出 bug。
大致上來說整個流程是:
- 建立假設
- 透過試著建立 reproducer 來修正與驗證假設;前後一共試了兩個假設,用三個方式去實驗。
- 假設與對應 fix 驗證後,回報上游做修正
建立 reproducer 的過程比較瑣碎,但也是比較精彩、能夠看出功力的地方,我摘要在下面。
建立 reproducer 的過程
整個過程前後一共試了兩個假設,用三個方式去實驗:
- 第一個假設:因為 「透過 tcp 溝通時,server 端長時間沒反應」
- 假設動機:使用 strace 去觀察那些用到 tcp protocol 的 process;在這個案例裡面又特別限縮在監聽 5240 port 的 process (因為 MaaS 使用 5240)。於是比較 wget 有無失效時的差異,發現失效的時候 epoll_wait 沒有回傳有效的 fd,而且監看的 process 有一個完全沒有動作、停在 read(),又跟其他 process 很不一樣。
- 檢查 system call
read
當時到底在讀哪個 fd,發現是一個試圖存取images-maas-io.sawo.canonical.com
的 socket。
- 檢查 system call
- 第一個驗證方式:寫一個簡單的、不會回應的 http server 來替代原本的
images-maas-io.sawo.canonical.com
。 - 第二個驗證方式:實驗後發現這個簡單的假 server 並不能每次都 reproduce issue,同時也注意到有時候會有 SSL error。決定換一個方式:重新部署一個類似的服務,直接在上面 dump 可能的 call trace。
- call trace 有顯示 SSL error 引發的 traceback
- 往上游找發現 python 本身在使用 ssl module 的時候預設不會 timeout,有人回報過類似的 SSL issue
- 第二個假設:捨棄第一個假設,改採用「SSL error 會引起 process hung」的假設
- 第三個驗證方式:基於第二個驗證方式得到的觀察最後使用防火牆設定來強制故意丟掉 server cert,試圖引起 SSL error
- 這個方式能夠每次重現 bug,包括「現象」與「call trace」。
- 利用這個方式作為 reproducer 測試假說的 fix (給 python request 加上 timeout);測試能夠解決 bug。
一行程式碼可能需要廣度與深度的知識與技巧
在 dann 除錯的過程中,最後也只是給程式碼的其中一行加上一個選項改寫預設值,就能有效解決問題。這其實是很經典的一個除錯旅程:「累積大量廣度與深度的知識之後,才知道要在最關鍵的地方做改動」。如果觀察很多知名的、大型的開源專案,往往都能看到這種「神奇的一行 fix」。如果只是事後回頭看 commit log,可能不會這個 commit 有什麼值得注意的地方,可說是魔鬼藏在細節當中。
這種「神奇的一行 fix」可能也是讓很多有心的開發者有 impostor syndrome 的原因:「為什麼同行高手都知道要這樣修,是我的話我完全沒頭緒!」,好像是某種「天才才有的靈感」才知道要這樣修正問題。這次有 dann 把整個過程寫下來,其實告訴我們高手也是會假設錯誤、或許高手和我們的差別是高手願意紮實地、系統性地一步一步做分析,並且平時願意在專業上做深度與廣度的累積,才能自在地在系統與高階程式之間轉換 context。
回頭參照 Effective Debugging 一書
如同之前提到的,我們這次也來檢視看看這次整個除錯過程中,有符合哪幾條 Effective Debugging 一書中的建議 (有些只是概念上沾到邊,不一定用了一樣的工具):
- chapter 1: high-level strategies
- item 3: confirm that preconditions and postconditions are satisfied
- item 4: drill up from the problem to the bug or down from the program's start to the bug
- item 5: find the difference between a known good system and a failing one (這條可說是我自己最常用、也最愛用的策略)
- item 6: use the software's debugging facilities
- item 7: diversify your build and execution environment
- chapter 2: general-purpose metods and practices
- item 10: enable the efficient reproduction of the problem
- chapter 3: general-purpose tools and techniques
- item 23: utilize command-line tool options and idioms
- chapter 8: debugging multi-threaded code
- item 60: analyze deadlocks with postmortem debugging
- item 62: uncover deadlocks and race conditions with specialized tools
- item 63: isolate and remove nondeterminism
順帶一提 Brendan Gregg 的 System Performance
類似於 Effective Debugging 一書,這本也是很不錯;不過我對這本的熟悉度比較低一些,之後也來試著寫點心得。我其實只是想說前陣子的第二版特價有被我搶到。