Goroutine 洩漏防治神器 goleak
推薦 goleak 的背景
goroutine 作為 golang 併發實現的核心組成部分,非常容易上手使用,但卻很難駕馭得好。我們經常會遭遇各種形式的 goroutine 洩漏,這些洩漏的 goroutine 會一直存活直到程式終結。它們的佔用的棧記憶體一直無法釋放、關聯的堆記憶體也不能被 GC 清理,系統的可用記憶體會隨洩漏 goroutine 的增多越來越少,直至崩潰!
goroutine 的洩漏通常伴隨著複雜的協程間通訊,程式碼評審和常規的單元測試通常更專注於業務邏輯正確,很難完全覆蓋 goroutine 洩漏的場景;而 pprof
等效能分析工具更多是作用於監控報警/故障之後的覆盤。我們需要一款能在編譯部署前識別 goroutine 洩漏的工具,從更上游把控工程質量。
goleak
(https://github.com/uber-go/goleak MIT 許可協議) 是 Uber 團隊開源的一款 goroutine 洩漏檢測工具,它可以非常輕量地整合到測試中,對於 goroutine 洩漏的防治和工程魯棒性的提升很有幫助。
防範勝於救災
goroutine 洩漏舉例
先舉個 goroutine 洩漏的例子;如下所示,leak
方法中的 ch
永遠沒有寫操作且不會關閉,讀取 ch
的 goroutine 一直處於阻塞狀態,這是一種很典型的 goroutine 洩漏。
func leak() {
ch := make(chan struct{})
go func() {
ch <- struct{}{}
}()
}
通常我們會為 leak
方法寫類似下面的測試:
func TestLeak(t *testing.T) {
leak()
}
用 go test
執行測試看看結果:
$ go test -v -run ^TestLeak$
=== RUN TestLeak
--- PASS: TestLeak (0.00s)
PASS
ok cool-go.gocn.vip/goleak 0.007s
測試不出意外地順利通過了,go 內建的測試顯然無法幫我們識別 leak
中的 goroutine 洩漏。
整合 goleak 測試
goleak
暴露的方法特別精簡,通常我們只需關注 VerifyNone
和 VerifyTestMain
兩個方法,它們也對應了 goleak
的兩種整合方式:
逐用例整合
在現有測試的首行新增 defer goleak.VerifyNone(t)
,即可整合 goleak 洩漏檢測:
func TestLeakWithGoleak(t *testing.T) {
defer goleak.VerifyNone(t)
leak()
}
這次的 go test
失敗了:
$ go test -v -run ^TestLeakWithGoleak$
=== RUN TestLeakWithGoleak
leaks.go:78: found unexpected goroutines:
[Goroutine 19 in state chan send, with cool-go.gocn.vip/goleak.leak.func1 on top of the stack:
goroutine 19 [chan send]:
cool-go.gocn.vip/goleak.leak.func1(0xc00008c420)
/Users/blanet/gocn/goleak/main.go:24 +0x35
created by cool-go.gocn.vip/goleak.leak
/Users/blanet/gocn/goleak/main.go:23 +0x4e
]
--- FAIL: TestLeakWithGoleak (0.45s)
FAIL
exit status 1
FAIL cool-go.gocn.vip/goleak 0.459s
測試報告顯示名為 leak.func1
的 goroutine 發生了洩漏(leak.func1
在這裡指的是 leak
方法中的第一個匿名方法),並將測試結果置為失敗。我們成功通過 goleak
找到了 goroutine 洩漏。
通過 TestMain 整合
如果覺得逐用例整合 goleak
的方式太過繁瑣或 “入侵” 性太強,不妨試試完全不改變原有測試用例,通過在 TestMain
中新增 goleak.VerifyTestMain(m)
的方式整合 goleak
:
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
這次的 go test
輸出如下:
$ go test -v -run ^TestLeak$
=== RUN TestLeak
--- PASS: TestLeak (0.00s)
PASS
goleak: Errors on successful test run: found unexpected goroutines:
[Goroutine 19 in state chan send, with cool-go.gocn.vip/goleak.leak.func1 on top of the stack:
goroutine 19 [chan send]:
cool-go.gocn.vip/goleak.leak.func1(0xc00008c2a0)
/Users/blanet/gocn/goleak/main.go:24 +0x35
created by cool-go.gocn.vip/goleak.leak
/Users/blanet/gocn/goleak/main.go:23 +0x4e
]
exit status 1
FAIL cool-go.gocn.vip/goleak 0.455s
可見,goleak
再次成功檢測到了 goroutine 洩漏,但與逐用例整合不同的是,goleak.VerifyTestMain
會先報告用例執行的結果,然後再進行洩漏分析。如果單次測試執行了多個用例且最終發生洩漏,那麼以 TestMain
方式整合的 goleak
並不能精準定位發生 goroutine 洩漏的用例,還需進一步分析。
goleak
提供了如下指令碼用於進一步推斷具體發生 goroutine 洩漏的用例,其本質是逐一執行所有用例進行分析:
# Create a test binary which will be used to run each test individually
$ go test -c -o tests
# Run each test individually, printing "." for successful tests, or the test name
# for failing tests.
$ for test in $(go test -list . | grep -E "^(Test|Example)"); do
./tests -test.run "^$test\$" &>/dev/null && echo -n "." || echo "\n$test failed"
done
總結
goleak
通過對執行時的棧分析獲取 goroutine 狀態,並設計了非常簡潔易用的介面與測試框架進行對接,是一款小巧強悍的 goroutine 洩漏防治利器。
當然,完備的測試用例支援是 goleak
發揮作用的基礎,大家還是要老老實實寫測試,穩穩當當搞生產!
參考資料
- https://github.com/uber-go/goleak
- https://pkg.go.dev/go.uber.org/goleak
- https://rakyll.org/leakingctx/
- https://github.com/golang/go/issues/6705
- https://medium.com/golangspec/goroutine-leak-400063aef468
- https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop
歡迎加入 GOLANG 中國社群:https://gocn.vip
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 分析記憶體洩漏和goroutine洩漏記憶體Go
- 如何防止 goroutine 洩露Go
- 如何防止 goroutine 洩露(二)Go
- go timer 洩漏Go
- 納尼,Java 存在記憶體洩洩洩洩洩洩漏嗎?Java記憶體
- Handler洩漏處理
- 解決記憶體洩漏(1)-ApacheKylin InternalThreadLocalMap洩漏問題分析記憶體Apachethread
- 記憶體洩漏問題分析之非託管資源洩漏記憶體
- js記憶體洩漏JS記憶體
- Android記憶體洩漏Android記憶體
- Android 記憶體洩漏Android記憶體
- jvm 記憶體洩漏JVM記憶體
- Java記憶體洩漏Java記憶體
- 記憶體洩漏的原因記憶體
- valgrind 記憶體洩漏分析記憶體
- 前端面試查漏補缺--(十三) 記憶體洩漏前端面試記憶體
- iOS檢測記憶體洩漏iOS記憶體
- Android記憶體洩漏場景Android記憶體
- ThreadLocal記憶體洩漏問題thread記憶體
- PHP 記憶體洩漏分析定位PHP記憶體
- 記憶體洩漏除錯工具記憶體除錯
- ThreadLocal真會記憶體洩漏?thread記憶體
- WebView引起的記憶體洩漏WebView記憶體
- Perfdog 玩轉記憶體洩漏記憶體
- JavaScript之記憶體洩漏【四】JavaScript記憶體
- .Net程式記憶體洩漏解析記憶體
- Andriod專案記憶體洩漏流程記憶體
- Java記憶體洩漏解決之道Java記憶體
- Android備忘錄《記憶體洩漏》Android記憶體
- 小心遞迴中記憶體洩漏遞迴記憶體
- vue使用中的記憶體洩漏Vue記憶體
- Android中的記憶體洩漏模式Android記憶體模式
- [譯] Swift 中的記憶體洩漏Swift記憶體
- snmp弱口令引起的資訊洩漏
- linux程式之記憶體洩漏分析Linux記憶體
- Android中常見的記憶體洩漏Android記憶體
- Swift的ARC和記憶體洩漏Swift記憶體
- 初步探究Android記憶體洩漏(1)Android記憶體