程序员社区

Go:协程泄漏探测器

【译文】原文地址

Go:协程泄漏探测器插图

使用APM监控goroutine的数量,可以容易的发现goroutine泄漏。下图是来自NewRelic(APM工具开发商)的一张监控goroutines的图片。

Go:协程泄漏探测器插图1

协程泄漏会导致协程数量持续增长直到服务器奔溃。然而,有很多方法来避免泄漏即使在代码部署之前也可以。

探测泄漏

Uber的Go团队在Github上面是一个很活跃的团队,开发了一个Goroutine泄漏探测器,是一个用于和单元测试集成的工具。这个包实际上是监测单元测试代码运行时协程的泄漏。以下是一个函数存在goroutine泄漏:

func leak() error {
    go func() {
        time.Sleep(time.Minute)
    }()
    return nil
}

下面的代码是该函数的测试代码:

func TestLeakFunction(t *testing.T)  {
    defer goleak.VerifyNone(t)
    if err := leak(); err != nil{
        t.Fatal("error not expected")
    }
}

运行测试代码会发现协程泄漏:

=== RUN   TestLeakFunction
    leaks.go:78: found unexpected goroutines:
        [Goroutine 19 in state sleep, with time.Sleep on top of the stack:
        goroutine 19 [sleep]:

上面的错误提示了两个错误信息:

  • 泄漏协程栈顶,状态信息。这个信息有助于快速发现是哪个协程泄漏。
  • 泄漏协程的ID,有助于跟踪器来可视化协程。可以使用go test ./... -trace trace.out

    Go:协程泄漏探测器插图2

    从跟踪的信息,可以查看goroutine的详细执行信息。

泄漏的携程被监测到后,而且还有关于泄漏的信息。现在我们明白它是如何工作的,因此需注意这个监测的缺陷。

内部

要能监测泄露,唯一的要求就是在测试代码最后面调用该库来检查泄漏的携程即可。实际上,它会监测所有goroutine而不仅仅是泄漏的goroutine。

首先检测器列出所有创建的goroutine。以下就是上面的列子所有goroutine列表:

Go:协程泄漏探测器插图3

Goroutine的栈信息来源于导出函数runtime.Stack(go标准库函数)。因此,任何人都可以拿到这些信息。

然后,从这个协程列表中,泄漏检测器就可以通过对这些信息进行分析,然后将属于标准库的协程删除。如下:

  • test包创建的goroutine用来运行测试用例的-前面例子中第二个goroutine。
  • runtime创建的goroutine,比如接收信号的协程。
  • 当前协程-上面例子中的第一个协程。
    最后,一旦过滤掉所有正常的协程,如果没有剩余的其他协程,意味着不存在协程泄漏。但我们发现这里面存在一些限制:
  • 第三方库或内部协程启动的后台goroutine,并没有很好的处理,可能会产生错误的报告。
  • 如果其他的测试用例中没有使用泄漏检测。如果再次运行,协程还存在就会被监测到,将会误报。

这个工具不是完美的,但是了解其中的可能性和限制有助于通过测试来检测泄漏,避免对已经部署到生产环境中的代码进行调试。

有趣的是这个方法被广泛用在了net/http包来检测泄漏协程。如下是一个例子:

Go:协程泄漏探测器插图4

再次看到,内部函数afterTest查看goroutine栈来检测存在的泄漏。

赞(0) 打赏
未经允许不得转载:IDEA激活码 » Go:协程泄漏探测器

一个分享Java & Python知识的社区