morgon has asked for the wisdom of the Perl Monks concerning the following question:

Venerable Monks,

I have some Moose-based objects that in memory form a graph with some circular references, some of which I weaken to ensure that the objects can be garbage-collected.

Now I want to create some unit-tests to ensure I did the weakening right- i.e. a unit-test that demonstates that instances are indeed garbage-collected.

Here is what I tried (assume Y to be a some Moose-based class):

use Test::More qw(no_plan); use Y; my $garbage_collected; *Y::DEMOLISH = sub { $garbage_collected = 1 }; { my $y = Y->new; }; ok($garbage_collected, "no memory-leak\n");

So basically the idea is to install a DEMOLISH-callback that is supposed to set a variable whenever an object is garbage-collected, then create an instance in a lexial scope (which should be garbage-collected immediately when leaving the block) and finally checking the variable set in the callback.

Unfortunately this does not work - the installed callback is never called.

This however works:

use Test::More qw(no_plan); use Y; my $garbage_collected; *Y::DESTROY = sub { $garbage_collected = 1 }; { my $y = Y->new; }; ok($garbage_collected, "no memory-leak\n");

The difference is that this time we don't install a DEMOLISH-callback (the Moose-destructor callback) but a DESTROY-callback (the standard Perl-destructor callback) so it seems that Moose's magic does not allow hacks like the above.

My question now is: Is this approach correct or are there better ways to achieve my goal?

Many thanks!

Replies are listed 'Best First'.
Re: unit-testing garbage collection with Moose
by ikegami (Patriarch) on Jun 30, 2009 at 21:54 UTC

    I don't know enough about Moose to explain why your attempt using DEMOLISH didn't work. I can suggest an alternative, though.

    Another way would be to get a weakened reference to the circular chain.

    my $weak_ref; { my $y = Y->new(); weaken( $weak_ref = $y ); $y->{__CIRCULAR__} = $y if $ENV{TEST_THE_TEST}; } ok(!defined($weak_ref), 'circular reference destruction');

    I think your method with the following tweak would be more reliable because it allows you to monitor all the objects, not just one. Change

    sub { $garbage_collected = 1 }

    to

    sub { ++$garbage_collected }

    Then you can construct a whole bunch of objects and make sure they all got destroyed.

Re: unit-testing garbage collection with Moose
by stvn (Monsignor) on Jul 01, 2009 at 05:27 UTC

    Without seeing the code for Y it would be hard to know exactly what is going wrong here. But I suspect that Y is being made immutable, which means Moose is compiling an inlined DESTROY method at compile time. So when you override DEMOLISH at runtime it is too late.

    Also, take a look at Test::Memory::Cycle, it will make your life easier for this stuff.

    -stvn
      The class I used had not been made immutable, but I think I was (again) bitten by using an out-of-date Moose.

      The DESTROY-approach actually works with the current Moose.