博客 / 詳情

返回

記一個 Powershell / cmd 終端下 Golang 程序卡死的問題

0 背景

這是一篇用來發泄情緒的文章,被一個愚蠢的問題折騰了好久。

不想看背景的可直接閲讀 1 問題 章節,或 2.3 演示

情況是這樣的,最近接手了一個 Windows 的 Golang 項目,它是一個多節點運行的服務。由於公司信息安全的特殊規定,我們必須將編譯後的程序放到固定的遠程虛擬機中運行測試,而不能在我們的本機上調試。有過經歷的讀者們可能知道,公司的那種虛擬機吧,無法連外網,且環境非常簡單。終端程序只有 Windows 自帶的 Windows Powershell 和 cmd。於是我選擇了 Powershell 作為我的調試終端。

平時我的工作開發環境是 Ubuntu。工作之外的個人開發,也多用的 Ubuntu。雖然有時也會在我的 Windows 上做開發,但終端程序使用的都是 Windows Terminal 這樣更現代的終端,或 tabby 這種第三方終端,亦或者是 VS Code 等編輯工具自帶的終端。可以説,我幾乎從來沒有使用 Windows Powershell 調試程序的經驗。

這是我接手該項目後,第一次調試該程序。而問題,就發生在 Windows Powershell 上。

1 問題

描述一下問題發生的經過。

因為這個服務需要多端通信,因此我分別在兩台 Windows 虛擬機上運行該程序,用的都是 Powershell 終端。所以我得在兩個虛擬機之間來回切換看運行情況;又因為該服務會將內部不同模塊的日誌分別打印在不同的地方,譬如有的日誌分佈在不同的日誌文件中,有的日誌則直接打印在終端標準輸出上。所以即使在同一個虛擬機裏,我也得在各種日誌文件以及 Powershell 終端之間切換查看運行日誌。

很忙的調試工作對吧,確實如此。於是在這繁忙的切換過程中,我發現了一個神奇的現象。

有時候,程序的某些邏輯突然就不執行了,彷彿整個程序卡死了。但是,有些日誌文件又依然在增加日誌,有的卻沒有。問題非必現,並且似乎沒有任何規律。可能突然某一瞬間就卡住了,也可能運行半個小時也無事發生。

這讓我百思不得其解。首先想到的肯定是程序本身的邏輯 bug,譬如,哪裏死鎖了。但無論我如何改代碼,問題依然在發生,且依然沒有任何業務邏輯上的規律。

2 原因及解決方法

2.1 原因

我完全沒有往終端本身的問題上去想,一是是因為沒有經驗,另外也是我自己想當然了。直到我注意到兩個現象:

  1. 日誌文件的日誌每次卡死時阻塞和繼續寫的可能不相同,但終端上的輸出一定是阻塞的;
  2. 每當這種時候我都需要按下兩次 ctrl + c 才能把程序終止。

兩個現象的同時出現,讓我終於認識到也許不是程序的問題,而是終端阻塞了程序。於是我開始去網上查,終於找到了問題的根本原因。

在 Windows 自帶的終端軟件中,無論是 cmd 還是 Powershell,都提供了一個叫快速編輯的功能。這個功能的初衷是讓用户更方便地選中終端上的文字。於是,在用户用鼠標點擊,選中終端上的內容時,哪怕只是一個空白字符,終端就會阻塞 stdout/stderr,導致程序在輸出終端日誌的地方阻塞,無法繼續執行。這時,需要用户按下回車鍵,或 ctrl + c,程序才會繼續執行。

我因為要來回切換終端、日誌文件,以及虛擬機本身,再加上虛擬機運行總歸會有一些不順暢,因此在操作的過程中可能無意間就觸發了終端的該特性,導致程序阻塞。

2.2 解決方法

解決方法也很簡單,在終端的屬性設置裏,取消勾選快速編輯即可,也就是不用這個功能。當然如果有條件的話,最好還是直接用更現代的終端軟件。

那麼那些更現代的終端軟件是如何解決用户選中終端上的輸出文字的呢?以 Window 自家的 Windows Terminal 為例,當用户做出鼠標拖選動作時,終端會停止自動下滾,讓終端上的輸出畫面停在用户拖選的時刻,但不阻塞程序。也就是説,程序依然在輸出文字,只是終端沒有切到程序最新的輸出行。

2.3 演示(ScreenToGif)

本節來演示上面討論的問題,以及該問題在 Windows Terminal 下的表現。演示用到了一段簡單的代碼:

func main() {
    num := 0
    for {
        log.Printf("Output, %d", num)
        num++
        time.Sleep(1 * time.Second)
    }
}

下圖演示了 cmd/powershell(我這裏用的是 cmd,powershell 同理)下問題的出現以及取消快速編輯後的效果:

可以看到,程序打印時,我的鼠標隨意點擊一個地方或拖選一個區域,就會使整個程序的輸出阻塞,直到我按下回車鍵,或 ctrl + c,阻塞才會解除。而當我在屬性中關閉了快速編輯後,再次點擊終端,則不會再出現阻塞問題。當然,此時也無法選擇輸出的文字了。

下面是 Windows Terminal 的表現:

很酷炫,Windows Terminal 中,不但可以方便地選中輸出的文字,而且還不會阻塞終端上的輸出。

3 結語

就,如果有條件的話,儘量就別用 Windows 自帶的 cmd 或 powershell 了。

文章內容有些羅嗦,畢竟有吐槽的成分在。但無論如何,希望本文的內容能幫助到每一個閲讀過本文的讀者,感謝閲讀和支持!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.