從 Git 問題排查到社區貢獻

關於 Git

Git 是目前世界上最爲廣泛使用的軟件版本控制系統(Version Control System),同時也是一個成熟及活躍的開源項目。

Git 最初是由 Linux 之父 Linus Torvalds 在 2005 年創建,至今已經迭代了 17 年。但任何程序都會有 Bug,對於 Git 這樣一個成熟的開源項目也不例外。引用 Linus 的話來說:

“Bugs will happen, if they don’t happen in hardware, they will happen in software and if they don’t happen in your software and they will happen in somebody else’s software.” Torvalds said.

“錯誤總會發生,如果它們不發生在硬件中,它們將發生在軟件中;如果它們不發生在你的軟件中,也會發生在其他人的軟件中。”

接下來,我們將從一個由 Git 引發的故障,一起來看看如何排查問題,並逐步進行 Git 社區貢獻。

Git 問題排查

現場還原

這個問題最初在 2022.6.8 被發現,代碼平臺服務收到報警,收到了大量的 Git 的代碼下載請求。在一番定位之後,發現在部分代碼下載的服務器上,發現了下圖的一連串 Git 進程:

從上圖我們可以看到一個關鍵點,PID  與 PPID 首尾相連,這是一個典型的嵌套循環問題。

在這個循環當中,產生了大量的 Git 進程,不但對代碼平臺產生了壓力,也對用戶本地產生了極大的困擾(本地的進程資源被大量佔用)。

所幸這個問題只要滿足觸發條件,就可以穩定復現。在獲取了異常的倉庫副本之後,我們就開始了問題剖析之旅。

問題剖析

首先,讓我們一起分析下異常倉庫的特點:

  1. .git/objects/info 目錄下,存在 commit-graph 圖文件。

  2. 倉庫使用了 .git/objects/info/alternates 來引入一些外部的倉庫。

  3. 使用了局部克隆(Partial Clone):在 .git/config 當中發現了一些痕跡。

[remote "origin"]
        promisor = true
        partialclonefilter = blob:none

爲了不對代碼平臺產生異常調用,我們嘗試在本地電腦上還原這個問題:

  1. 我們將異常倉庫副本,解壓到 work 目錄下。

  2. 通過 git clone --bare {url} remote.git 我們下載了一個遠程倉庫。

  3. 通過 git -C work remote set-url origin "$(pwd)remote.git"work 的遠程倉庫設置成本地。

接下來,我們就可以通過執行異常命令來複現問題。

爲了更好地跟蹤,我們可以通過 GIT_TRACE=1 或者 GIT_TRACE2_EVENT="$(pwd)/event.trace" 來開啓過程追蹤。

爲了更好地定位問題,我們也可以考慮修改 Git 源碼,通過 trace_print 增加一些額外的調用日誌。此外,我們還可以藉助 LLDB / GDB 幫助我們進行本地觀察與調試。

最終,在一番努力之後,一個可怕的死循環調用鏈逐漸浮出水面:

 git fetch
     -> do_fetch_pack_v2() 
         -> deref_without_lazy_fetch() 
             -> lookup_commit_in_graph() 
                 -> repo_has_object_file() 
                     -> promisor_remote_get_direct() 
             -> fetch_objects() 
                 -> git fetch (😄,我們又回到了最初相遇的地點,開始新的輪迴)

原因分析

讓我們就着調用鏈路,再來回顧一下原因:

  1. 由於本地是一個的局部克隆的倉庫,存在一些對象缺失的情況,而訪問這些缺失的對象,就可能產生懶加載拉取。

  2. 而在懶加載的過程中,引用查找 deref_without_lazy_fetch() 方法會嘗試先去從 commit-graph 提交圖當中尋找對象來進行一些加速。

  3. 而 lookup_commit_in_graph() 當中,使用了 repo_has_object_file() 來判斷對象是否存在於 Git 倉庫當中,這個方法,可能引發懶加載拉取本地缺失的對象,開啓一輪新的git fetch

  4. 而新一輪的 git fetch ,由於本地存在 commit-graph,在引用查找中,再次進入了步驟 2~3 的循環當中。

這裏有一個關鍵點,問題是由於提交記錄存在於 commit-graph,而在 Git 對象倉庫中缺失導致的。這種情況通常並不會發生。結合倉庫使用了 objects/info/alternates 來引入外部倉庫,我們來猜想一下問題是如何產生的。

猜想驗證

如下圖當中,通過 objects/info/alternates ,我們引入了一個外部的共享倉庫,來減少當前倉庫的對象存儲。同時,我們創建了一個 commit-graph,來加速本地的提交記錄訪問。

由於共享的倉庫與本地倉庫維護的引用列表(分支、標籤等統稱爲引用)並不相同,在觸發 git gc或者自動 gc 之後,由於提交記錄 B 是一個不可達的對象(不存在於任何引用當中),導致了存在於 commit-graph 當中的共享提交記錄 B 被清理了。

這時候,只要在當前倉庫觸發了懶加載,就會引發前面的死循環調用鏈,產生大量的 Git 進程,最終拖垮用戶本地及遠端服務。

雖然從原因來看,這個問題的產生,某種意義上是由於不合理使用 alternates 及 commit-graph 導致的,但我們的確很難限制用戶該如何使用。於是,我們決定尋求 Git 社區的幫助。

尋求 Git 社區幫助

我們決定,派你出發,開始這次的 Git 社區之旅。

芝麻開門

提到開源社區,大家都會不約而同想到 Github[1],比較這可是全球最大的 “程序員交友社區”。

可當你興沖沖地跑到 https://github.com/git/git 準備提交一個 Issue (問題)時,嗯?說好的 Issues 入口去哪了?

再看右邊的描述:

雖然,你可以通過 Github 上的 GitGitGadget 機器人幫助我們進行補丁提交,可是你現在只是想請教社區一個問題,該怎麼辦呢?

曲徑通幽

在 git-scm.com 上,通過閱讀 《MyFirstContribution》[2],你找到了尋求幫助的三個路徑:

1、git@vger.kernel.org

這是主要的 Git 項目郵件列表,用於進行代碼審查、版本公告、設計討論等。

這也是目前 Git 社區最主要的交流方式,Github 上的 GitGitGadget 也是幫助你做郵件轉換的工作。

任何有興趣貢獻的人,都在這裏發佈問題。

但需要注意的是,Git 列表需要純文本電子郵件,並且在回覆郵件時更喜歡內聯和底部發布。

2、git-mentoring@googlegroups.com

此郵件列表面向新的貢獻者,並被創建爲在主列表的公衆視線之外發布問題和接收答案的地方。對幫助指導新人特別感興趣的資深貢獻者出現在列表中。爲了避免搜索索引器,查看消息需要組成員身份;任何人都可以加入,無需批准。

3、#git-devel[3] 在 Libera Chat[4] 上

這個 IRC 頻道用於 Git 貢獻者之間的對話。

如果有人當前在線並且知道你的問題的答案,你可以實時獲得幫助;或者你也可以閱讀  scrollback[5] 以查看是否有人回答了你。但 IRC 不允許離線私信,因此如果你嘗試私信某人然後退出 IRC,他們將無法回覆你。

你隨意翻開如下鏈接中看到了其中一天的討論:

https://colabti.org/irclogger/irclogger_log/git-devel?date=2022-06-20

新手上路

接下來你選擇使用郵件的方式,請求社區的幫助,於是,你奮筆疾書,寫了一封郵件。

當你通過 git@vger.kernel.org 發送郵件到社區之後,就可以通過 https://lore.kernel.org/git 這個公共的收件箱列表,找到剛纔發送的郵件。

除此之外,你驚喜地發現,新世界的大門正式向你敞開:

由於 “Git 列表需要純文本電子郵件,並且在回覆郵件時更喜歡內聯和底部發布”,因此在使用郵件服務時請特別注意:

https://kernel.org/pub/software/scm/git/docs/git-send-email.html

你可以通過點擊 permalink 找到如下的命令行:

  git send-email \
    --in-reply-to={Message-ID} \
    --to=someone@email.com
    --cc=git@vger.kernel.org \
    --subject='Re: [PATCH v2 0/1] scalar: move to the top-level, test, CI and "install" support' \
    /path/to/YOUR_REPLY

請特別注意攜帶 in-reply-to,只有這樣你的郵件纔會被很好地組織在一起,避免丟失一些上下文。

小試牛刀

在與社區交流的過程中,你不斷深入,找到了可行的修復方案。我想,此刻的你一定躍躍欲試,想盡快將自己的修復方案發給社區。但如果你不想石沉大海,或是迎來一些不必要的批評,請你先保持冷靜,除了前面提到的《MyFirstContribution》,你還需要再做一些準備工作。

1、📚 閱讀 Git 的《補丁提交》[6]

https://github.com/git/git/blob/master/Documentation/SubmittingPatches

《補丁提交》將指導你如何建立自己的工作,比如該基於 maint(穩定版本)還是該基於 master ,又或者是指導你該如何清晰地表達這個補丁的價值。

在這個文檔當中,你將學到關於補丁製作與提交的方方面面。

2、📚 閱讀 Git 的《編碼指南》[7]

https://github.com/git/git/blob/master/Documentation/CodingGuidelines

《編碼指南》將幫助你瞭解一些 Git 非常較真的編碼規範,從下面的一個例子你就可以看出他們對空格有多麼的較真:

        (incorrect)
        cat hello > world < universe
        echo hello >$world

        (correct)
        cat hello >world <universe
        echo hello >"$world"

3、學習如何編寫 Git 的測試用例

https://github.com/git/git/blob/master/t/README

Git 當中,使用一套以 shell 腳本爲主,名爲 Sharness 的測試框架。這套測試框架在 2011 年創建,並從 Git 的 test-lib.sh 當中派生出來,成爲了一個獨立的項目:https://github.com/chriscool/sharness

4、勇敢發送你的補丁

當你一切準備就緒,確認 Github CI 也亮起了綠燈,勇敢地發送你的補丁吧,開始屬於你的 Git 貢獻之旅。

小結

我們從一個 Git 引發的故障開始,瞭解了 Git 問題排查的過程,在找到並復現問題之後,嘗試尋求 Git 社區的幫助,並小試牛刀,向 Git 社區提交了我們的補丁。

Git 自 2005 年開始發展至今,正是由於其廣泛的使用,以及一大批優秀的社區貢獻者,讓 Git 變得越來越好。使用 Git,發現問題並改進它,在這個過程中你一定可以得到別樣的收穫。

加入我們

我們是字節跳動代碼技術團隊,致力於打造先進的代碼領域產品,包括但不限於代碼存儲、代碼協同、代碼分析等方向。

如果你是一個懂代碼,有技術夢想的工程師,歡迎加入我們,一起來探索更好的開發理念與實踐。

發送簡歷郵件到 webinfra@bytedance.com 即可。

附錄

  1. Github:https://github.com

  2. 《MyFirstContribution》:https://git-scm.com/docs/MyFirstContribution / https://github.com/git/git/blob/main/Documentation/MyFirstContribution.txt

  3. #git-devel:https://web.libera.chat/#git-devel

  4. Libera Chat:https://libera.chat

  5. scrollback:https://colabti.org/irclogger/irclogger_logs/git-devel

  6. CodingGuidelines:https://github.com/git/git/blob/master/Documentation/CodingGuidelines

  7. SubmmitingPatches:https://github.com/git/git/blob/master/Documentation/SubmittingPatches

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/sKACp9XVZks5Kfz-pWJMkg