それでもやっぱりTemplateToolkitがすき
TemplateToolkitが大好き。TemplateToolkit無しでは生きられない。TemplateToolkitは僕の人生に楽しみと時間を与えてくれました。
そんなTemplateToolkitに日々感謝の念を忘れない僕ですが、今回ばっかりはちょっと我が目を疑う出来事に遭遇。
Template::Plugin::Filterに潜む困ったちゃん。
結果から言うとFilterの扱いで油断するとメモリリークします。くれぐれも油断めされるな。
まぁこういうFilterを作ってみた訳です
package Matsumo::TTPlugin::MatsumoFilter; use base qw(Template::Plugin::Filter); sub init { my $self = shift; $self->install_filter('matsumo'); return $self; } sub filter { my ($self, $text, $args, $config) = @_; $text =~ s/hoge/fuga/g; return $text; } 1;
何に使うんだとかはさておきまして。
このFilterを使ってみます
use strict; use Template; local $/ = undef; my $template = <DATA>; Template->new({PLUGIN_BASE => 'Matsumo::TTPlugin'})->process(\$template, {}, \my $out) or die; print $out; __END__ [% USE MatsumoFilter %] filtering="[% 'hogehogefugafuga' | matsumo %]"
結果
filtering="fugafugafugafuga"
でましたフィルタリングされてますね。フガフガフガフガ。
さぁ稼動開始。おっとメモリ急騰!!なんだなんだなんだ!!なんだってーーーー!!
という事態に見舞われました。
という訳で原因究明
以下のように手を加えてループするようにしました。Apacheのプロセスに近いっちゃ近い感じ。
psの結果でメモリの消費量を見てみようという感じです。
(他に良いメモリの見方あったら誰か教えて下さい。)
id:usuihiro1978さんに教えてもらったDevel::Leak::ObjectでLeakをチェックと万全の体制。
use strict; use Devel::Leak::Object qw(GLOBAL_bless); use Template; local $/ = undef; print STDERR `ps aux | head -n1`; my $template = <DATA>; for (0..$ARGV[0]) { Template->new({PLUGIN_BASE => 'Matsumo::TTPlugin'})->process(\$template, {}, \my $out) or die; print $out; print STDERR `ps aux | grep "perl leak.pl"` if $_ == 0 || $_ == $ARGV[0]; } __END__ [% USE MatsumoFilter %] filtering="[% 'hogehogefugafuga' | matsumo %]"
とりあえずpsの結果を見てみよう
$ perl leak.pl 500 > /dev/null USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND yumatsumo 43690 2.0 1.1 5704 5184 p0 S+ 12:54AM 0:00.16 /usr/local/bin/perl leak.pl 500 yumatsumo 43690 50.7 2.1 9944 9428 p0 S+ 12:54AM 0:01.88 /usr/local/bin/perl leak.pl 500
※grep "perl leak.pl"自体のプロセスも引っかかっちゃってるけど割愛してます。
10回まわして結果チェック
$ perl leak.pl 10
Tracked objects by class: Config 1 Matsumo::TTPlugin::MatsumoFilter 11 Template::Context 11 Template::Filters 11 Template::Grammar 11 Template::Parser 11 Template::Plugins 11 Template::Provider 11 Template::Stash::XS 11
来てますねぇ。リークしてますねぇ。
さぁ犯人を捜せ
勿体ぶってもしょうがないので結論はTemplate::Plugin::Filterの中にいました。
先に出てきたフィルター内のメソッドinstall_filterは
sub install_filter { my ($self, $name) = @_; $self->{ _CONTEXT }->define_filter( $name => $self->factory() ); return $self; }
こういうメソッドでfactoryに処理が投げられてます。で、factoryメソッドの中に↓のような箇所。
return $self->{ _STATIC_FILTER } ||= sub { $self->filter(shift); };
さぁ違和感感じませんか。どうですか?
そう$self->{_STATIC_FILTER}の中で$selfが参照されてると。
これはまずいという事で解決策を模索。
答えはid:miyagawaさんのTemplate::Plugin::CommaのChangesの中に有りました。(他にもあったけど)
0.03 Thu Nov 14 13:47:51 JST 2002
* Fixed memory leak due to Template::Plugin::Filter
というわけで2002年に既にソリューションされてたみたいですorz
という事でdiff見てみた。
http://search.cpan.org/diff?from=Template-Plugin-Comma-0.02&to=Template-Plugin-Comma-0.03#lib/Template/Plugin/Comma.pm
なるほど。
- 親クラスをTemplate::Plugin::FilterからTemplate::Pluginに
- initをnewに
- $self->install_filterを$context->define_filterに
とか言う感じで対応。
という訳で出来ました
変更点は上のリスト通り。
package Matsumo::TTPlugin::MatsumoFilter; use base qw(Template::Plugin); sub new { my ($self, $context) = @_; $context->define_filter('matsumo', \&matsumo); return $self; } sub matsumo { my $text = shift; $text =~ s/hoge/fuga/g; return $text; } 1;
さぁチェック!
この瞬間が一番好きだったりします。チェック!チェック!チェック!
$ perl leak.pl 500 > /dev/null USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND yumatsumo 49501 0.0 1.1 5684 5164 p0 S+ 1:26AM 0:00.16 /usr/local/bin/perl leak.pl 500 yumatsumo 49501 85.6 1.1 5688 5172 p0 S+ 1:26AM 0:01.90 /usr/local/bin/perl leak.pl 500
大丈夫そう!
$ perl leak.pl 10
Tracked objects by class: Config 1
おっけ〜。バッチコ〜イ。
という訳で無事解決しました。
みなさまもお気をつけあれ。(って常識だったりするのかしら)
まとめ
それでもやっぱりTemplateToolkitがすき!!