可用于单元测试的 Netty 4/5 确定性缓冲区泄漏检测

Netty 4/5 deterministic buffer leak detection that can be used in unit tests

我阅读了很多有关 Netty4 中缓冲区泄漏检测的文章,对我来说,似乎没有确定的方法来检测单元测试中的此类泄漏。

然而,这种功能对于单元测试的重要性如此之大,以至于没有明确的指导方针如何去做,感觉很不对。

此外,大多数来源包括原始文件 http://netty.io/wiki/reference-counted-objects.html 通过给出模糊的提示让事情变得非常混乱:

"The following output shows a leak from our unit test (XmlFrameDecoderTest.testDecodeWithXml()):"

这听起来好像有一种方法可以在单元测试中确定性地检测缓冲区分配器中的泄漏,而实际上并没有这样的东西,正如 Trustin Lee 自己在他的回答中指出的那样(link 下面)。

难怪这篇文章被各种来源转载了数十次,这些来源根本不知道,只是复制粘贴单词而不进行测试。

*) Trustin Lee 建议在以下主题中 运行 在一些繁忙的工作负载下将应用程序运行 30 秒。 Netty 4/5 does not actually detect resource leak of bytebuf? 但是,这不会为我触发检测或 ResourceLeakDetector 的任何输出。

*) 我还尝试了以下主题中建议的 GC 技巧 How to force garbage collection in Java? 但这也没有任何区别。

GC 是如此不可预测,以至于很难想象如何利用 ResourceLeakDetector 来创建干净彻底的缓冲区泄漏单元测试。

*) 另一种方法是在测试 运行ning 时为每个创建的 ByteBuf 测试 refCnt。 但有时不可能获得每个这样的引用,因为接口可能将 String 声明为输入参数,然后其实现将在内部创建和释放 ByteBuf 实例,并且该引用将无法进行单元测试,但是如果发布没有不会发生它会产生在单元测试中无法检测到的泄漏。

*) 我也找不到一种简单的方法来从分配器中获取所有现有缓冲区的列表,否则可以只检查其中每一个缓冲区的 refCnt。


我想知道是否有人可以分享哪些最佳实践以确定性方式工作并且可以实际用于单元测试以始终如一地发现使用 Netty 缓冲区分配器的大型代码库中的缓冲区泄漏。

到目前为止,对我来说,单元测试似乎对此毫无用处,除非您 运行 在单元测试中长时间使用全尺寸服务器(顺便说一句,这也没有'不能保证任何事情,只是增加理论上的机会)。 据我所知,没有充分的理由存在这种对测试的限制,但我们有我们拥有的。

互联网上有太多关于这个主题的令人困惑的信息,可悲的是,这些信息来自 Netty 文档本身,我真的希望以直截了当和清晰的方式陈述事实。

即使我的问题的答案是 "it's impossible",仅提供此文本可能会节省一些人大量的研究时间。


P.S。一个非常简单的示例来演示缺少输出。 如果有人能告诉我需要进行哪些更改才能使此代码产生泄漏输出,我将不胜感激。

http://gist.github.com/codekrolik/e55b8ece07270f40aad85f691696fe6a

对于单元测试,System.gc() 对我有用。 (我不确定它有多可靠,但它似乎足够可靠,足以在测试中引起足够的 gc 来捕获泄漏)。然而,对我来说,单元测试中的泄漏主要是因为我忘记在测试中释放缓冲区(而不是因为服务器代码本身有泄漏)。

正如您提到的,集成 and/or 负载测试可能有助于发现服务器中的任何泄漏。

所以我设法使单元测试对我有用。

机制如下:

1) 按照 https://github.com/netty/netty/issues/5275

中的建议,创建一个禁用缓存的 PooledBufferAllocator
PooledByteBufAllocator alloc = new PooledByteBufAllocator(true, 1, 1, 8192, 11, 0, 0, 0);

2) 确保所有的引导程序都使用这个分配器

一个。客户

Bootstrap clientBootstrap = new Bootstrap();
clientBootstrap.option(ChannelOption.ALLOCATOR, alloc);

b。服务器

ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.option(ChannelOption.ALLOCATOR, alloc)
    .childOption(ChannelOption.ALLOCATOR, alloc);

3) 测试完成后,检查直接缓冲区和堆缓冲区的缓冲区泄漏

assertEquals(0, getActiveDirectBuffers(alloc));
assertEquals(0, getActiveHeapBuffers(alloc));

int getActiveDirectBuffers(PooledByteBufAllocator alloc) {
    int directActive = 0, directAlloc = 0, directDealloc = 0;
    for (PoolArenaMetric arena : alloc.directArenas()) {
        directActive += arena.numActiveAllocations();
        directAlloc += arena.numAllocations();
        directDealloc += arena.numDeallocations();
    }
    System.out.println("directActive " + directActive + " directAlloc " + directAlloc + " directDealloc " + directDealloc);
    return directActive;
}

int getActiveHeapBuffers(PooledByteBufAllocator alloc) {
    int heapActive = 0, heapAlloc = 0, heapDealloc = 0;
    for (PoolArenaMetric arena : alloc.heapArenas()) {
        heapActive += arena.numActiveAllocations();
        heapAlloc += arena.numAllocations();
        heapDealloc += arena.numDeallocations();
    }
    System.out.println("heapActive " + heapActive + " heapAlloc " + heapAlloc + " heapDealloc " + heapDealloc);
    return heapActive;
}