为什么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 无法知道这些泄漏是故意的,所以你得到了我认为你可以称之为误报的东西。
- 当我说“从未被释放”时,我的意思是“直到全球毁灭才被释放”。然后一切都被释放了。