如何定位 golang 程式 hang 死的 bug
之前在 golang 群裡有人問過為什麼程式會莫名其妙的 hang 死然後不再響應任何請求。單核 cpu 打滿。
這個特徵和我們公司的某個系統曾經遇到的情況很相似,內部經過了很長時間的定位分析總結,期間還各種閱讀 golang 的 runtime 和 gc 程式碼,最終才定位到是業務裡出現了型別下面這樣的程式碼:
package main
import "runtime"
func main() {
var ch = make(chan int, 100)
go func() {
for i := 0; i < 100; i++ {
ch <- 1
if i == 88 {
runtime.GC()
}
}
}()
for {
// the wrong part
if len(ch) == 100 {
sum := 0
itemNum := len(ch)
for i := 0; i < itemNum; i++ {
sum += <-ch
}
if sum == itemNum {
return
}
}
}
}
在 main goroutine 裡迴圈判斷 ch 裡是否資料被填滿,在另一個 goroutine 裡把 100 條資料塞到 ch 裡。看起來程式應該是可以正常結束的對不對?
感興趣的話你可以自己嘗試執行一下。實際上這個程式在稍微老一些版本的 golang 上是會卡死在後面這個 for 迴圈上的。原因呢?
使用:
GODEBUG="schedtrace=300,scheddetail=1" ./test1
應該可以看到這時候 gcwaiting 標記為 1。所以當初都懷疑是 golang gc 的 bug。。但最終折騰了半天才發現還是自己的程式碼的問題。
因為在 for 迴圈中沒有函式呼叫的話,編譯器不會插入排程程式碼,所以這個執行 for 迴圈的 goroutine 沒有辦法被調出,而在迴圈期間碰到 gc,那麼就會卡在 gcwaiting 階段,並且整個程式永遠 hang 死在這個迴圈上。並不再對外響應。
當然,上面這段程式在最新版本的 golang 1.8/1.9 中已經不會 hang 住了 (實驗結果,沒有深究原因)。某次更新說明中官方聲稱在密集迴圈中理論上也會讓其它的 goroutine 有被排程的機會,那麼我們選擇相信官方,試一下下面這個程式:
package main
import (
"fmt"
"io"
"log"
"net/http"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
go server()
go printNum()
var i = 1
for {
// will block here, and never go out
i++
}
fmt.Println("for loop end")
time.Sleep(time.Second * 3600)
}
func printNum() {
i := 0
for {
fmt.Println(i)
i++
}
}
func HelloServer(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
}
func server() {
http.HandleFunc("/", HelloServer)
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
執行幾秒之後 curl 一發:
curl localhost:12345
感覺還是不要再相信官方了。研究研究之後不小心寫出了這樣的 bug 怎麼定位比較好。首先分析一下這種型別 bug 發生時的程式特徵:
1. 卡死在 for 迴圈上
2. gcwaiting=1
3. 沒有系統呼叫
由於沒有系統呼叫,不是系統呼叫導致的鍋,所以我們沒有辦法藉助 strace 之類的工具看程式是不是 hang 在系統呼叫上。而 gcwaiting=1 實際上並不能幫我們定位到問題到底出現在哪裡。
然後就剩卡死在 for 迴圈上了,密集的 for 迴圈一般會導致一個 cpu 核心被打滿。如果之前做過系統程式設計的同學應該對 perf 這個工具很瞭解,可以使用:
perf top
對 cpu 的使用情況進行取樣,這樣我們就可以對 cpu 使用排名前列的程式函式進行定位。實際上 perf top
的執行結果也非常直觀:
99.52% ffff [.] main.main
0.06% [kernel] [k] __do_softirq
0.05% [kernel] [k] 0x00007fff81843a35
0.03% [kernel] [k] mpt_put_msg_frame
0.03% [kernel] [k] finish_task_switch
0.03% [kernel] [k] tick_nohz_idle_enter
0.02% perf [.] 0x00000000000824d7
0.02% [kernel] [k] e1000_xmit_frame
0.02% [kernel] [k] VbglGRPerform
你看,我們的程式實際上是卡在了 main.main 函式上。一發命令秒級定位。
媽媽再也不用擔心我的程式不小心寫出死迴圈了。實際上有時候我的一個普通迴圈為什麼變成了死迴圈並不是像上面這樣簡單的 demo 那樣好查,這時候你還可以用上 delve,最近就幫 jsoniter 定位了一個類似上面這樣的 bug:
> https://github.com/gin-gonic/gin/issues/1086
從 perf 定位到函式,再用 pid attach 到程式,找到正在執行迴圈的 goroutine,然後結合 locals 的列印一路 next。
問題定位 over。
最後一行小字:感謝之前毛總在 golang 群的指導。
本文行文倉促,應該還是難免疏漏,如果你覺得哪裡寫的有問題,或者對文末提到的毛總頂禮膜拜,再或者對即將進入 k8s 的 json 解析庫 jsoniter 非常感性趣,想要深入瞭解 jsoniter 作者大牛的心路歷程,那麼你有下面幾個選擇:
1.加入 b 站和毛總學習先進姿勢水平
2.加入滴滴和 jsoniter 作者大牛 @taowen 做同事
3.加入滴滴來噴本文作者
簡歷可以傳送到: caochunhui@didichuxing.com
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- APM RUEI processor處理程式hang死處理方法
- Oracle DBLink bug引發的故障(Session Hang Memory leak)OracleSession
- MySQL 一個讓你懷疑人生的hang死現象MySql
- 測試過程中如何快速定位一個 bug
- 測試過程中如何快速定位一個bug
- 簡單的linux Oops定位到bug程式碼行操作實踐LinuxOOP
- java如何避免程式死鎖Java
- 延遲密碼驗證特性引起的資料庫HANG死及當機密碼資料庫
- Web測試中定位bug方法Web
- golang 指標型別引起的神奇 bugGolang指標型別
- golang在程式語言排行榜上排名第10,請不要說golang已死。Golang
- Fixed定位如果脫離Viewport會產生什麼樣的bug?如何解決?View
- 萬萬沒想到,一個 MongoDB.Driver 的 bug 導致 .NET5 程式死鎖!MongoDB
- 線上BUG:MySQL死鎖分析實戰MySql
- golang中關於死鎖的思考與學習Golang
- Golang 千萬別把key或者password寫死到程式碼裡面去Golang
- 阿里二面:如何定位&避免死鎖?連著兩個面試問到了!阿里面試
- 如何寫一段死鎖程式碼
- 4種Golang併發操作中常見的死鎖情形Golang
- 程式設計師是如何從複雜的程式碼裡找到 bug 的?程式設計師
- 2018-08-21網站請求+BUG定位網站
- 線上出bug了?別怕,這麼定位!
- golang如何優雅的編寫事務程式碼Golang
- Golang 中由零值和 gob 庫的特性引起的 BUGGolang
- JAVA CPU100%與執行緒死鎖定位Java執行緒
- [BUG反饋]function get_parent_category() 死迴圈FunctionGo
- MySQL:一個奇怪的hang案例MySql
- golang 執行時死鎖排查和檢測Golang
- 小程式定位
- 資料產品Bug定位流程與實用方法
- 如何在 NET 程式萬種死法中有效的生成 Dump (下)
- 如何在 NET 程式萬種死法中有效的生成 Dump (上)
- 面試:什麼是死鎖,如何避免或解決死鎖;MySQL中的死鎖現象,MySQL死鎖如何解決面試MySql
- 如何正確的找BUG
- 前端頁面測試如何定位漏測程式碼前端
- 如何將bug殺死在搖籃裡?解讀壓測必經之路JMeter分散式的技術點JMeter分散式
- RAC節點hang住, oracle bug導致了cpu過高,無法啟動叢集隔離Oracle
- 資料庫伺服器記憶體不夠,DBW0 hang死導致sqlplus連不上資料庫伺服器記憶體SQL
- golang 中 channel 的詳細使用、使用注意事項及死鎖分析Golang