goroutine泄漏检测神器---goleak

2022-06-20 19:06:55 浏览数 (2)

goroutine泄漏检测神器---goleak

在日常开发中,go 出去的goroutine通常伴随着死循环,这些goroutine可能处于阻塞状态,一直运行,直到进程结束。

对于线上服务来说,一直是在运行的,除非panic重启等,不然一旦出现goroutine泄漏,资源被一直占用,cpu/内存将会直线陡增,对于线上服务影响巨大,通常出了问题,可以采用pprof来进行分析,日常开发中,如果能做到边开发,边检测出这种泄漏问题,将会非常有用,这里便会引用到了goroutine离线检测工具:goleak。

1.使用

代码语言:javascript复制
func TestA(t *testing.T) {
 defer goleak.VerifyNone(t)

 // test logic here.
}

在进行单元测试时可以直接使用,非常方便。

例如:下面是有异常的goroutine,使用goleak检测的结果:

代码语言:javascript复制
found unexpected goroutines:
[Goroutine 23 in state chan receive, with go.uber.org/goleak.TestIgnoreCurrent.func2.1 on top of the stack:
goroutine 23 [chan receive]:
go.uber.org/goleak.TestIgnoreCurrent.func2.1(0xc000118c60)
 /go_proj/goleak/leaks_test.go:111  0x34
created by go.uber.org/goleak.TestIgnoreCurrent.func2
/go_proj/goleak/leaks_test.go:110  0x11d
]

2.原理

像这种检测工具具体怎么实现的呢?比较好奇,就阅读了一下源码,发现源码非常的少,里面有很多技巧与学习的东西。

第一个点:如何检测?

这里的检测是尝试20次,每次最大时间睡眠100ms(允许耗时的goroutine运行)。每次检测过程为:过滤掉传入的goroutine,通过runtime.Stack获取到各种状态的goroutine,20次之后如果还有剩余的goroutine,那么就是有问题的,输出出来就好了。

内部实现的一些技巧:

  • 过滤函数

对外暴露IgnoreTopFunction接口允许传入goroutine关键字符串,内部会去做匹配,用户不用关心细节,内部函数作为参数,调用addFilter,还支持传入当前goroutine,调用IgnoreCurrent。

代码语言:javascript复制
func IgnoreTopFunction(f string) Option {
 return addFilter(func(s stack.Stack) bool {
  return s.FirstFunction() == f
 })
}
func IgnoreCurrent() Option {
 excludeIDSet := map[int]bool{}
 for _, s := range stack.All() {
  excludeIDSet[s.ID()] = true
 }
 return addFilter(func(s stack.Stack) bool {
  return excludeIDSet[s.ID()]
 })
}
func addFilter(f func(stack.Stack) bool) Option {
 return optionFunc(func(opts *opts) {
  opts.filters = append(opts.filters, f)
 })
}
  • 获取所有goroutine的栈信息

内部通过定义stack结构体,创建私有的getStacks进行解析,getStacks会解析出多个stack结构,内部实现原理为读取runtime.Stack的所有goroutine信息。

代码语言:javascript复制
// Stack represents a single Goroutine's stack.
type Stack struct {
 id            int
 state         string
 firstFunction string
 fullStack     *bytes.Buffer
}

最后,会获取到每个goroutine的id、状态、函数名,栈信息。

0 人点赞