前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go每日一库之119:goleak(goroutine 泄漏检测)

Go每日一库之119:goleak(goroutine 泄漏检测)

作者头像
luckzack
发布2023-09-30 08:37:34
4350
发布2023-09-30 08:37:34
举报

背景

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 泄漏。

代码语言:javascript
复制
func leak() {
    ch := make(chan struct{})
    go func() {
        ch <- struct{}{}
    }()
}

通常我们会为 leak方法写类似下面的测试:

代码语言:javascript
复制
func TestLeak(t *testing.T) {
    leak()
}

用 go test 执行测试看看结果:

代码语言:javascript
复制
$ 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 的两种 集成方式:

  1. 逐用例集成 在现有测试的首行添加 defer goleak.VerifyNone(t),即可集成 goleak 泄漏检测:
代码语言:javascript
复制
func TestLeakWithGoleak(t *testing.T) {
    defer goleak.VerifyNone(t)
    leak()
}

这次的 go test 失败了:

代码语言:javascript
复制
$ 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 泄漏。

  1. 通过 TestMain 集成 如果觉得逐用例集成 goleak 的方式太过繁琐或 “***” 性太强,不妨试试完全不改变原有测试用例,通过在 TestMain中添加 goleak.VerifyTestMain(m) 的方式集成 goleak:
代码语言:javascript
复制
func TestMain(m *testing.M) {
    goleak.VerifyTestMain(m)
}

这次的 go test 输出如下:

代码语言:javascript
复制
$ 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 泄漏的用例,其本质是逐一执行所有用例进行分析:

代码语言:javascript
复制
# 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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • goroutine 泄漏举例
  • 集成 goleak 测试
  • 总结
相关产品与服务
腾讯云服务器利旧
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档