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

Update (Aug 02):I've edited my original post to include downloadable code and clarify the test case.

I have a Catalyst applications, which uses Catalyst::Model::KiokuDB (v0.12) with the CouchDB-backend. The applications leaks slightly for every request that access (not even use) the model. The controller (listed below) I'm using for reproducing this problem is simply calling $context->model() and then returns an empty reply, without ever using the model.

I've tracked it down to something related to Catalyst::Model::KiokuDB::save_scope(). Commenting out the call to save_scope() in ACCEPT_CONTEXT() removes the leak completely.

I've tried every single object and memory debug tool I know of, without any luck:

So now I have two questions for the collective wisdom of the PerlMonks:

  1. Does anyone have any ideas about what's going on?
  2. Does anyone have any ideas on how to debug this? Ideally I would like a tools or library, which can track every single malloc() call around this specific code block.

Steps to reproduce

  1. Create a new Catalyst application called TestApp:
    # catalyst.pl TestApp
  2. Place Leaktest.pm in TestApp/lib/TestApp/Controller:
    package TestApp::Controller::Leaktest; use strict; use warnings; use Moose; use utf8; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } sub index :Path :Args(0) { my ($self, $context) = @_; my $model = $context->model(); $c->response->body(""); $c->response->status(400); 1; } sub modules :Local { my( $self, $c, @path ) = @_; { no strict; # We're accessing $VERSION by symbolic reference no warnings 'uninitialized'; # The $VERSION might not be defin +ed $c->response->body(join "\n", map { s!/!::!g; # We're getting a file name.. +. s!.pm$!!; # ...and want a module name sprintf "%-80s %s", $_, ${"${_}::VERSIO +N"} } sort keys %INC); } $c->response->content_type("text/plain"); $c->stash->{current_view} = 'Nop'; 1; } sub exit :Local :Args(0) { exit(0); } 1;
  3. Place KiokuDB.pm in TestApp/lib/TestApp/Model:
    package TestApp::Model::KiokuDB; use Moose; BEGIN { extends 'Catalyst::Model::KiokuDB'; } __PACKAGE__->config( model_class => 'KiokuX::Model', model_args => { dsn => 'couchdb::uri=http://127.0.0.1:5984/lb-local', } ); 1;
  4. Place leaktest in TestApp/script:
    #!/bin/bash URL="http://localhost:3000/leaktest" EXIT="http://localhost:3000/leaktest/exit" APPNAME="testapp_server.pl" PID=$(pgrep -f ${APPNAME}) if [[ -z $PID ]]; then echo "Please start ${APPNAME} first!" exit 1; fi echo -e "HITS\tSZ\tRSS" echo -ne "0\t" ps -p ${PID} -o size,rss | sed -ne '2s/[ ]\+/\t/gp' for OUTER in $(seq 1 10); do for INNER in $(seq 1 1000); do ( curl $URL 2>&1) > /dev/null done echo -ne "$((${OUTER} * 1000))\t" ps -p ${PID} -o size,rss | sed -ne '2s/[ ]\+/\t/gp' done ( curl ${EXIT} 2>&1) > /dev/null
  5. Start TestApp in one console:
    # TestApp/script/testapp_server.pl
  6. Run leaktest in another console:
    # TestApp/script/leaktest

My modules

The following list was obtained from TestApp with
# curl -s http://localhost:3000/leaktest/modules | sed -ne '/.*\(Catalyst\|KiokuDB\).*[0-9\.]\+/p':

Catalyst + 5.80032 Catalyst::Action + -1, set by base.pm Catalyst::Action::RenderView + 0.16 Catalyst::Devel + 1.31 Catalyst::Model::KiokuDB + 0.12 Catalyst::Plugin::ConfigLoader + 0.30 Catalyst::Plugin::Static::Simple + 0.29 Catalyst::Runtime + 5.80032 KiokuDB + 0.52 KiokuDB::Backend::CouchDB + 0.10
I'll be happy to provide more details, but I was reluctant with posting the complete 449 lines long list.

Replies are listed 'Best First'.
Re: Weird memory leak in Catalyst application using Catalyst::Model::KiokuDB
by Corion (Patriarch) on Aug 01, 2011 at 14:05 UTC

    A first thing would be to make your test case(s) accessible to us. This means that you'll have to at least find out one combination of module versions that has the memory leak. Preferrably, these are all the latest module versions.

    As a second step, I would try to eliminate as much of Catalyst and KiokuDB as possible, and call the offending part of the code in a tight loop as to exaggerate the memory consumption as far as possible.

    From my cursory look at Catalyst::Model::KiokuDB::save_scope(), it calls another subroutine, ->setup_scope_guard(), which already has caveats about memory leaks. So I would continue from the code in there - most likely, some callback is closing over variables that it should release.

    Are you sure that your diagnostic tools likel Devel::Monitor and Devel::Cycle are applicable resp. work? Personally, I prefer to override the DESTROY method of classes to track when an object gets destroyed:

    my $old_destroy = \&Catalyst::Model::KiokuDB::DESTROY; *Catalyst::Model::KiokuDB::DESTROY = sub { warn "$_[0] destroyed"; goto &$old_destroy; };

    together with a block at the end of the main program telling me when global destruction starts.

      I've edited my original post to make the code downloadable and written a step by step test case.

      I've eliminate all but the defaults from Catalyst and KiokuDB by making the test case create a new application from scratch. The test case doesn't prune Catalyst further as it should (in theory) work just like that.

      I've noticed the comment in Catalyst::Model::KiokuDB::setup_scope_guard() too. But Devel::Monitor claims that all variables declared in setup_scope_guard() and the callback in Scope::Guard are freed. Obviously, something isn't freed probably, but I'm starting to think it isn't an object, I'm looking for.

      Devel::Monitor does something very similar to overriding DESTROY, only it does it by using some tie-magic, I think. But the result is the same; tracking when an object gets destroyed on-screen. Anyway, I'll try overriding DESTROY for a few candidates, just in case.

Re: Weird memory leak in Catalyst application using Catalyst::Model::KiokuDB
by zwon (Abbot) on Aug 01, 2011 at 14:02 UTC

      How would Valgrind help with Perl data structures?

      As far as I'm aware, Valgrind can only check whether a C program overwrites memory it has not allocated, and maybe can check whether a C program allocates memory and then loses the pointer(s) to that piece of memory. If you have a valid Perl scalar, Perl will keep a valid (C) pointer to it, and will release the memory during Global Destruction. If that's true, then Valgrind will/can not flag this Perl value as ever lost, even though it may be a memory leak in the sense that there is no Perl structure from the running set of Perl variables to access that piece of memory.

        I recently spent quite a lot of time trying to find memory leak in Perl code, and finally ran it through Valgrind and discovered that it is YAML::XS. For sure it will not help with Perl structures, but I never said that.

      Test::LeakTrace might have put me on the right track, and it looks like I can narrow it further down to KiokuDB:

      use strict; use warnings; use Test::More; use Test::Exception; use Test::LeakTrace; use KiokuX::Model; my $model; eval { $model = KiokuX::Model->new(dsn => 'couchdb::uri=http://127.0.0 +.1:5984/lb-local'); }; plan skip_all => 'Cannot connect to CouchDB' if ($@); { no_leaks_ok { { my $scope = $model->new_scope; } } 'new_scope must not leak'; } done_testing;

      gives me:

      ot ok 1 - new_scope must not leak (leaks 2 <= 0) # Failed test 'new_scope must not leak (leaks 2 <= 0)' # at KiokuX_Model.t line 17. # '2' # <= # '0' # leaked SCALAR(0x4159810) from /usr/lib64/perl5/vendor_perl/5.12.4/Ki +okuDB/LiveObjects.pm line 191. # 190: # 191: $known->remove($scope); # 192: # SV = IV(0x4159808) at 0x4159810 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 0 # leaked ARRAY(0x4201180) from /usr/lib64/perl5/vendor_perl/5.12.4/Kio +kuDB/LiveObjects.pm line 260. # 259: # 260: $self->_known_scopes->insert($child); # 261: # SV = PVAV(0x4578968) at 0x4201180 # REFCNT = 2 # FLAGS = () # ARRAY = 0x4580b40 # FILL = 0 # MAX = 3 # ARYLEN = 0x0 # FLAGS = (REAL) # Elt No. 0 # SV = IV(0x4159808) at 0x4159810 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 0 1..1 # Looks like you failed 1 test of 1.

      Valgrind also complained about something in Set::Object, so it might be the source of the problem. I'll keep digging.

        I think I got my leak narrowed down now, and it looks like Set::Object::Weak is the problem. I've written the following short test script:

        use strict; use warnings; use Config; use Test::More; use Test::LeakTrace; use Set::Object; { package Foo; use Moose; 1; } { no strict; note join ' ', map {$Config{$_}} qw(osname archname); note 'perl version ', $]; note $_,'-',${"${_}::VERSION"} for qw{Moose Set::Object Test::Leak +Trace}; } my $set; { $set = Set::Object->new; no_leaks_ok { { my $obj = Foo->new; $set->insert($obj); $set->remove($obj); } } 'Testing Set::Object for leaking'; } { $set = Set::Object::Weak->new; no_leaks_ok { { my $obj = Foo->new; $set->insert($obj); $set->remove($obj); } } 'Testing Set::Object::Weak for leaking'; } done_testing;

        which gives me:

        # linux x86_64-linux # perl version 5.012004 # Moose-2.0202 # Set::Object-1.28 # Test::LeakTrace-0.13 ok 1 - Testing Set::Object for leaking (leaks 0 <= 0) not ok 2 - Testing Set::Object::Weak for leaking (leaks 2 <= 0) # Failed test 'Testing Set::Object::Weak for leaking (leaks 2 <= 0)' # at Set_Object.t line 39. # '2' # <= # '0' # leaked SCALAR(0xdad3a0) from Set_Object.t line 37. # 36: $set->insert($obj); # 37: $set->remove($obj); # 38: } # SV = IV(0xdad398) at 0xdad3a0 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 0 # leaked ARRAY(0x1163cd0) from Set_Object.t line 36. # 35: my $obj = Foo->new; # 36: $set->insert($obj); # 37: $set->remove($obj); # SV = PVAV(0x1394b00) at 0x1163cd0 # REFCNT = 2 # FLAGS = () # ARRAY = 0x13ac740 # FILL = 0 # MAX = 3 # ARYLEN = 0x0 # FLAGS = (REAL) # Elt No. 0 # SV = IV(0xdad398) at 0xdad3a0 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 0 1..2 # Looks like you failed 1 test of 2.

        Can anyone else here verify this? And if not, could I kindly ask you to post the top 5 lines of the output (the os, arch and versions)?

Re: Weird memory leak in Catalyst application using Catalyst::Model::KiokuDB
by parmus (Novice) on Aug 04, 2011 at 10:07 UTC