如何定位 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死處理方法
- oradebug分析oracle hangOracle
- 10.2出現reliable message資訊導致程式hang死
- MySQL 一個讓你懷疑人生的hang死現象MySql
- oradebug處理DB hang情況
- 測試過程中如何快速定位一個 bug
- 測試過程中如何快速定位一個bug
- 9i資料庫hang死分析處理資料庫
- Oracle DBLink bug引發的故障(Session Hang Memory leak)OracleSession
- 如何定位死迴圈或高CPU使用率(linux)Linux
- java如何避免程式死鎖Java
- 如何批量殺死當前的MySQL程式MySql
- 如何定位導致Crash的程式碼位置
- smon程式互為死鎖案例--oracle一個bug處理Oracle
- golang在程式語言排行榜上排名第10,請不要說golang已死。Golang
- 【原】關於SQLServer死鎖的診斷和定位SQLServer
- IE CSS Bug系列:IE7中:hover絕對定位的BugCSS
- 阿里二面:如何定位&避免死鎖?連著兩個面試問到了!阿里面試
- Web測試中定位bug方法Web
- 如何理解golang的併發程式設計Golang程式設計
- golang 指標型別引起的神奇 bugGolang指標型別
- 使用oradebug dump hanganalyze分析oracle hang系列一Oracle
- 使用oradebug dump hanganalyze 分析oracle hang系列二Oracle
- 使用oradebug dump hanganalyze 分析oracle hang系列三Oracle
- 線上BUG:MySQL死鎖分析實戰MySql
- golang中關於死鎖的思考與學習Golang
- 系統出現cursor: mutex X等待導致例項HANG死Mutex
- 延遲密碼驗證特性引起的資料庫HANG死及當機密碼資料庫
- 漫畫:Git 二分 Debug,火速定位出錯程式碼!Git
- Golang 千萬別把key或者password寫死到程式碼裡面去Golang
- Golang寫的程式注入一些版本資訊,Debug速度自然快不少Golang
- 用 debugger 學習 golangGolang
- oradebug分析oracle hang或慢_sqlplus_prelimOracleSQL
- 殺死Oracle死鎖程式Oracle
- 萬萬沒想到,一個 MongoDB.Driver 的 bug 導致 .NET5 程式死鎖!MongoDB
- 如何寫一段死鎖程式碼
- 4種Golang併發操作中常見的死鎖情形Golang
- 程式設計師如何修復婚姻的Bug?程式設計師