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

Test::LeakTrace说代码泄漏。我不明白。

我不明白 的输出Test::LeakTrace,这篇文章太长了。一些来自测试系统的泄漏,但其他的?不。

这是代码。

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

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


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

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

state $cache = { };

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

泄漏的 SV是在它们创建的范围结束后未释放的 SV。这些 SV 包括全局变量和内部缓存。例如,如果您在跟踪块中调用一个方法,perl 可能会为该方法准备一个缓存。因此,要跟踪真正的泄漏,no_leaks_ok()leaks_cmp_ok()多次执行一个块。

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

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


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

以上是为什么Test::LeakTrace说这个Perl代码正在泄漏内存?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>