Devel::Cycleがいい感じ。

id:yumatsumoさんのところで触れられているTemplate::Plugin::Filterのメモリリーク問題ですが、Devel::Cycleをためそうとして諸事情(?)によりためせなかったのですが改めて試してみるといい感じです。

まずはリークするソース

package Template::Plugin::Leak;
use strict;
use base 'Template::Plugin::Filter';

sub init {
    my $self = shift;

    # first arg can specify filter name
    $self->install_filter($self->{ _ARGS }->[0] || 'leak');

    return $self;
}


sub filter {
    my ($self, $text, $args, $config) = @_;

    return uc $text;
}

1

利用側のコード

use strict;
use Template;
use lib 'lib';
use Devel::Cycle;

my $template =<<__TEMPLATE__;
[% USE Leak %]
[% FILTER leak %]
lower case characteres
[% END %]
__TEMPLATE__

my $tt = Template->new();
$tt->process( \$template ) or die $tt->error;

find_cycle( $tt );

実行結果

Cycle (1):
             $Template::A->{'SERVICE'} => \%Template::Service::B        
        $Template::Service::B->{'CONTEXT'} => \%Template::Context::C        
        $Template::Context::C->{'LOAD_FILTERS'} => \@D                           
                               $D->[0] => \%Template::Filters::E        
        $Template::Filters::E->{'FILTERS'} => \%F                           
                          $F->{'leak'} => \&G                           
                     $G variable $self => \%Template::Plugin::Leak::H   
        $Template::Plugin::Leak::H->{'_CONTEXT'} => \%Template::Context::C        

Cycle (2):
             $Template::A->{'SERVICE'} => \%Template::Service::B        
        $Template::Service::B->{'CONTEXT'} => \%Template::Context::C        
        $Template::Context::C->{'LOAD_FILTERS'} => \@D                           
                               $D->[0] => \%Template::Filters::E        
        $Template::Filters::E->{'FILTERS'} => \%F                           
                          $F->{'leak'} => \&G                           
                     $G variable $self => \%Template::Plugin::Leak::H   
        $Template::Plugin::Leak::H->{'_STATIC_FILTER'} => \&G    

最後の2行あたりでG <-> Hで循環しているというのがよくわかります。ちゃんと{_STATIC_FILTER}も検出してくれていますし。
全体的に見るにはDevel::Leak::Objectで、場所を特定するのにDevel::Cycleでと使い分けると便利かも。

Devel::Cycleをテストハーネスに組み込んでくれるモジュールhttp://search.cpan.org/~petdance/Test-Memory-Cycle-1.04/もあるらしく、これを習慣付けるといいなぁと思って試してみると以下のエラーがでていることに気づく。(もともと出ていたみたいだったけど望みどおりの結果で満足してスルーしていた・・・)

Not a SCALAR reference at /usr/local/share/perl/5.8.8/Devel/Cycle.pm line 124.

調べてみるとバグレポートがでていて、クロージャがらみのエラーのようだ。
http://rt.cpan.org/Public/Bug/Display.html?id=25360

パッチが添付されてされていたので試してみたけど

Undefined subroutine &Devel::Cycle::_find_cycle_ called at /usr/local/share/perl/5.8.8/Devel/Cycle.pm line 98.

といって怒られた。
もう眠いので今日は断念。