Where Did Those Leaks Come From?

Ovid on 2008-09-09T08:35:19

Tracking down memory leaks is no fun and frankly, our application is leaking like a stuck pig. In the process of trying to track all of this down, I wrote and submitted a little change for Devel::Object::Leak. This change lets you add callbacks to a global bless. So right now I want to know where my schema objects are being created:

package leaks;

use Data::Dumper::Simple;
$Data::Dumper::Indent   = 1;
$Data::Dumper::Sortkeys = 1;

use Devel::Leak::Object GLOBAL_bless => sub {
    my ($reference, $class) = @_;
    return unless $class && $class->isa('Pips3::Schema');
    my ($package, $file, $line) = caller(2);

    warn Dumper($package, $file, $line, $class);
    warn $/;
};

1;

And then I run a test:

perl -Mleaks t/acceptance.t t/acceptance/import/import_id.yml

Then I get a handy list of where all of those things are getting instantiated (trimmed for clarity):

$package = 'DBIx::Class::Schema';
$file = '...deps/lib/perl5//DBIx/Class/Schema.pm';
$line = 707;
$class = 'Pips3::Schema';

$package = 'DBIx::Class::Schema';
$file = '...deps/lib/perl5//DBIx/Class/Schema.pm';
$line = 589;
$class = 'Pips3::Schema';

$package = 'Catalyst::Model::DBIC::Schema';
$file = '...deps/lib/perl5//Catalyst/Model/DBIC/Schema.pm';
$line = 288;
$class = 'Pips3::Schema';

Well, that told me some of what I needed to know, but not enough. However now that I can add callbacks to bless, I can dump stack traces and really figure out what's going on.

(What's really interesting is the discovery that there are three schema objects being created here, when I thought there should only be only or two).


Test::Weaken

jeffreykegler on 2008-09-09T17:55:21

Perhaps you'll find Test::Weaken helpful. There's one routine, poof(). You give it a subroutine that creates your object, it runs the subroutine and frees the return values, then examines them to see which haven't actually been freed.

This sounds contradictory, but the trick Test::Weaken uses is weak references -- that way you try to free something, but still have a reference to anything that didn't get freed. Most useful for you might be that poof() returns a list of the objects which didn't get freed and you can actually examine them. If that's enough to tell you where they were created, you're done. Otherwise, you can trying adding tags that let you know where the object came from.

Ignore the warning that it's alpha software. I've used Test::Weaken a lot. It's part of the Parse::Marpa regression test and has caught a lot of leaks for me. Some day soon I'll change the documentation to describe Test::Weaken as released.

hope this helps,

jeffrey kegler

Why do it via global bless?

Alias on 2008-09-10T01:56:39

One thing that strikes me as curious is that you want to hook the global, instead of just Hook::LexWrap'ing the specific new method. It does seem a bit like overkill.

Adam K