为什么 Test::LeakTrace 说这个 Perl 代码正在泄漏内存?

Why does Test::LeakTrace say this Perl code is leaking memory?

Test::LeakTrace 表示代码泄漏。没看懂。

我不理解 Test::LeakTrace 的输出,对于这个 post 来说太长了。测试系统有一些泄漏,但其他泄漏?编号

这是代码。

use 5.26.0;
use warnings;
use Test::More;
use Test::LeakTrace;

sub spawn {
    my %methods = @_;
    state $spawned = 1;
    my $object  = bless {}, "Spawned::Class$spawned";
    $spawned++;
    while ( my ( $method, $value ) = each %methods ) {
        no strict 'refs';
        *{ join '::', ref($object), $method } = sub { $value };
    }
    return $object;
}

no_leaks_ok {
    my $spawn = spawn( this => 2 );
    is( $spawn->this, 2 );
}
'no leaks';

done_testing;

我遇到这样奇怪的事情:

# leaked SCALAR(0x7f9b41a069c0) from leak.pl line 11.
#   10:        $spawned++;
#   11:        while ( my ( $method, $value ) = each %methods ) {
#   12:            no strict 'refs';
# SV = IV(0x7f9b41a069b0) at 0x7f9b41a069c0
#   REFCNT = 1
#   FLAGS = (IOK,pIOK)
#   IV = 2

还有这个:

# leaked GLOB(0x7f9b411b22a0) from leak.pl line 9.
#    8:        state $spawned = 1;
#    9:        my $object  = bless {}, "Spawned::Class$spawned";
#   10:        $spawned++;
# SV = PVGV(0x7f9b41a29530) at 0x7f9b411b22a0
#   REFCNT = 1
#   FLAGS = (MULTI)
#   NAME = "Class4::"
#   NAMELEN = 8
#   GvSTASH = 0x7f9b4081a0a8    "Spawned"
#   FLAGS = 0x2
#   GP = 0x7f9b40534720
#     SV = 0x0
#     REFCNT = 1
#     IO = 0x0
#     FORM = 0x0
#     AV = 0x0
#     HV = 0x7f9b41a24730
#     CV = 0x0
#     CVGEN = 0x0
#     GPFLAGS = 0x0 ()
#     LINE = 9
#     FILE = "leak.pl"
#     EGV = 0x7f9b411b22a0  "Class4::"

对我来说没有任何意义。引用计数为 1。

你的代码泄露了。它们是故意泄漏,但仍然泄漏。

您创建了一个永远不会被释放的包。[1]您在其中创建了一个永远不会被释放的 glob。您为这个 glob 分配了一个永远不会被释放的 sub。 sub 捕获一个变量,因此它永远不会被释放。

该模块正在执行它的工作并告诉您这件事。


我遇到了一些意外,证实了上述情况。此答案的其余部分识别并解释了它们。

我将使用这个程序 (a.pl):

use 5.010;
use Test::More tests => 1;
use Test::LeakTrace;

sub f {
   state $spawned = 1;
   my $object  = bless {}, "Spawned::Class$spawned" if $ARGV[0] & 1;
   $spawned++                                       if $ARGV[0] & 2;
   delete $Spawned::{"Class".($spawned-1)."::"}     if $ARGV[0] & 4;
}

如果我们$spawned++;但不祝福:

$ perl a.pl 1
1..1
ok 1 - leaks 0 <= 0

预期。


如果我们祝福而不祝福$spawned++;:

$ perl a.pl 2
1..1
ok 1 - leaks 0 <= 0

咦!?我们创建了全球符号。这些不应该被认为是泄漏吗?那么,为什么 OP 会产生泄漏呢?我会回到这个。


如果我们两者都做:

$ perl a.pl 3
1..1
not ok 1 - leaks 8 <= 0
#   Failed test 'leaks 8 <= 0'
#   at a.pl line 11.
#     '8'
#         <=
#     '0'
#
# [snip]

咦?!怎么突然提到我们创造的全球符号?!我的意思是,这是我们所期望的,但我们也在上面期望它。我会回到这个。


最后,我们还将撤消所做的更改。

$ perl a.pl 7
1..1
ok 1 - leaks 0 <= 0

正如预期的那样,如果我们释放我们对全局符号 table 所做的添加,它不再报告任何泄漏。


现在让我们来解决我提出的问题。

想象一下,如果你做了类似

的事情
state $cache = { };

您不希望该散列被报告为泄漏,即使它从未被释放。为此,Test::LeakTrace 两次评估测试块,忽略第一次调用的泄漏。

Leaked SVs are SVs which are not released after the end of the scope they have been created. These SVs include global variables and internal caches. For example, if you call a method in a tracing block, perl might prepare a cache for the method. Thus, to trace true leaks, no_leaks_ok() and leaks_cmp_ok() executes a block more than once.

这就是为什么 perl a.pl 2 没有导致任何泄漏报告。

但是 perl a.pl 3 和 OP 的代码(故意)在每次调用时都会泄漏,而不仅仅是第一次。 Test::LeakTrace 无法知道这些泄漏是有意的,所以我想你得到的可能是误报。


  1. 当我说“从未释放”时,我的意思是“在全球毁灭之前从未释放”。然后一切都被释放了。