| Test2::Tools::Refcount(3pm) - phpMan
Test2::Tools::Refcount(3pm) User Contributed Perl Documentation Test2::Tools::Refcount(3pm)
NAME
"Test2::Tools::Refcount" - assert reference counts on objects
SYNOPSIS
use Test2::Tools::Refcount;
use Some::Class;
my $object = Some::Class->new();
is_oneref( $object, '$object has a refcount of 1' );
my $otherref = $object;
is_refcount( $object, 2, '$object now has 2 references' );
DESCRIPTION
The Perl garbage collector uses simple reference counting during the normal execution of a
program. This means that cycles or unweakened references in other parts of code can keep
an object around for longer than intended. To help avoid this problem, the reference count
of a new object from its class constructor ought to be 1. This way, the caller can know
the object will be properly DESTROYed when it drops all of its references to it.
This module provides two test functions to help ensure this property holds for an object
class, so as to be polite to its callers.
If the assertion fails; that is, if the actual reference count is different to what was
expected, either of the following two modules may be used to assist the developer in
finding where the references are.
· If Devel::MAT is installed, this test module will use it to dump the state of the
memory after a failure. It will create a .pmat file named the same as the unit test,
but with the trailing .t suffix replaced with -TEST.pmat where "TEST" is the number of
the test that failed (in case there was more than one).
See the examples below for more information.
FUNCTIONS
is_refcount
is_refcount( $object, $count, $name )
Test that $object has $count references to it.
is_oneref
is_oneref( $object, $name )
Assert that the $object has only 1 reference to it.
refcount
$count = refcount( $object )
Returns the reference count of the given object as used by the test functions. This is
useful for making tests that don't care what the count is before they start, but simply
assert that the count hasn't changed by the end.
use Test2::Tools::Refcount import => [qw( is_refcount refcount )];
{
my $count = refcount( $object );
do_something( $object );
is_refcount( $object, $count, 'do_something() preserves refcount' );
}
EXAMPLE
Suppose, having written a new class "MyBall", you now want to check that its constructor
and methods are well-behaved, and don't leak references. Consider the following test
script:
use Test::More tests => 2;
use Test2::Tools::Refcount;
use MyBall;
my $ball = MyBall->new();
is_oneref( $ball, 'One reference after construct' );
$ball->bounce;
# Any other code here that might be part of the test script
is_oneref( $ball, 'One reference just before EOF' );
The first assertion is just after the constructor, to check that the reference returned by
it is the only reference to that object. This fact is important if we ever want "DESTROY"
to behave properly. The second call is right at the end of the file, just before the main
scope closes. At this stage we expect the reference count also to be one, so that the
object is properly cleaned up.
Suppose, when run, this produces the following output (presuming Devel::MAT::Dumper is
available):
1..2
ok 1 - One reference after construct
not ok 2 - One reference just before EOF
# Failed test 'One reference just before EOF'
# at ex.pl line 26.
# expected 1 references, found 2
# SV address is 0x55e14c310278
# Writing heap dump to ex-2.pmat
# Looks like you failed 1 test of 2.
This has written a ex-2.pmat file we can load using the "pmat" shell and use the
"identify" command on the given address to find where it went:
$ pmat ex-2.pmat
Perl memory dumpfile from perl 5.28.1 threaded
Heap contains 25233 objects
pmat> identify 0x55e14c310278
HASH(0)=MyBall at 0x55e14c310278 is:
XX(via RV) the lexical $ball at depth 1 of CODE() at 0x55e14c3104a0=main_cv, which is:
X XXthe main code
XX(via RV) value {self} of HASH(2) at 0x55e14cacb860, which is (*A):
XX(via RV) value {cycle} of HASH(2) at 0x55e14cacb860, which is:
itself
(This document isn't intended to be a full tutorial on Devel::MAT and the "pmat" shell;
for that see Devel::MAT::UserGuide).
From this output, we can see that the constructor was well-behaved, but that a reference
was leaked by the end of the script - the reference count was 2, when we expected just 1.
Reading the trace output, we can see that there were 2 references that could be found -
one stored in the $ball lexical in the main program, and one stored in a HASH. Since we
expected to find the $ball lexical variable, we know we are now looking for a leak in a
hash somewhere in the code. From reading the test script, we can guess this leak is likely
to be in the bounce() method. Furthermore, we know that the reference to the object will
be stored in a HASH in a member called "self".
By reading the code which implements the bounce() method, we can see this is indeed the
case:
sub bounce
{
my $self = shift;
my $cycle = { self => $self };
$cycle->{cycle} = $cycle;
}
From reading the tracing output, we find that the HASH this object is referenced in also
contains a reference to itself, in a member called "cycle". This comes from the last line
in this function, a line that purposely created a cycle, to demonstrate the point. While a
real program probably wouldn't do anything quite this obvious, the trace would still be
useful in finding the likely cause of the leak.
If "Devel::MAT::Dumper" is not available, then these detailed traces will not be produced.
The basic reference count testing will still take place, but a smaller message will be
produced:
1..2
ok 1 - One reference after construct
not ok 2 - One reference just before EOF
# Failed test 'One reference just before EOF'
# at demo.pl line 16.
# expected 1 references, found 2
# Looks like you failed 1 test of 2.
BUGS
· Temporaries created on the stack
Code which creates temporaries on the stack, to be released again when the called
function returns does not work correctly on perl 5.8 (and probably before). Examples
such as
is_oneref( [] );
may fail and claim a reference count of 2 instead.
Passing a variable such as
my $array = [];
is_oneref( $array );
works fine. Because of the intention of this test module; that is, to assert reference
counts on some object stored in a variable during the lifetime of the test script,
this is unlikely to cause any problems.
ACKNOWLEDGEMENTS
Peter Rabbitson <ribasushi AT cpan.org> - for suggesting using core's "B" instead of
"Devel::Refcount" to obtain refcounts
AUTHOR
Paul Evans <leonerd AT leonerd.uk>
perl v5.20.2 2023-10-25 Test2::Tools::Refcount(3pm)
|