|
|
||
まず基礎知識として、Perl で webapp をかいている場合 SEGV する原因は
といったところです。(本体のバグについてはPerl 5.8 ではバグっていても 5.14 などではすでに修正されているバグも多いかとおもいます)。
というわけで、テンプレートエンジンがSEGVする場合のうち、90% はテンプレートエンジン自体の問題ではありません。そこでまずは SEGV する原因をしらべるわけですが、SEGV する原因をしらべるには、gdb をつかうのが簡単です。
% gdb --args perl `which plackup` app.psgi
などとして起動してから実際に SEGV をおとし、gdb がとまったところですかさず 'bt' コマンドをうちます(bt=backtrace)。この時、C レベルのスタックトレースがとれますが、このときに表示されているスタックトレースのログの一番上の方にのこっている部分に注目します。
今回の場合は Xslate の stacktrace ばかりが表示されていたので、あきらかに問題は xslate でした。他の XS モジュールの名前があらわれていた場合には xslate 以外が原因ですので、そちらをおってください。
問題があきらかになったらあとはコードをけずっていきます。base class の中身を問題の部分にむけてひきあげるとかそういうのを地道にやっていってどんどんコードをけずります。
最小のケースができあがったらあとは作者のふジゴロウにおくりつければそのうちなおります。
(このエントリー、途中でかいてるうちにめんどくさくなってしまいました)
Plack::Request の現在の実装では、uri と base を一切キャッシュしておらず、これらのメソッドを多様すると、URI オブジェクトの生成コストが支配的となってしまうことがある(実際に、私のプロジェクトでこの問題が発生した)。
そこで、Variable::Magic を利用し、データの変更時にキャッシュを clear するようにしつつ、キャッシュをおこなうようにしてみた。
さて、いかがだろうか。なお、ベンチ結果はいかのとおり。
Rate orig fast
orig 15582/s -- -99%
fast 1048176/s 6627% --
package Plack::Request::Cached; use strict; use warnings; use parent qw/Plack::Request/; use Variable::Magic qw/cast wizard VMG_OP_INFO_NAME/; use Scalar::Util qw/refaddr weaken/; our %MAP; my $wiz = wizard( store => sub { delete $MAP{ refaddr( $_[0] ) }->{uri}; delete $MAP{ refaddr( $_[0] ) }->{base}; }, ); sub new { my $class = shift; my $self = $class->SUPER::new(@_); cast %{$self->env}, $wiz; my $addr = refaddr $self->env; $MAP{$addr} = $self; weaken($MAP{$addr}); return $self; } sub DESTROY { my $self = shift; delete $MAP{refaddr $self->env}; } sub uri { $_[0]->{uri} ||= do { my $self = shift; my $base = $self->_uri_base; my $path = $self->env->{PATH_INFO} || ''; $path .= '?' . $self->env->{QUERY_STRING} if defined $self->env->{QUERY_STRING} && $self->env->{QUERY_STRING} ne ''; $base =~ s!/$!! if $path =~ m!^/!; URI->new($base . $path)->canonical; }; } sub base { $_[0]->{base} ||= do { my $self = shift; URI->new($self->_uri_base)->canonical; }; } sub _uri_base { my $self = shift; my $env = $self->env; my $uri = ($env->{'psgi.url_scheme'} || "http") . "://" . ($env->{HTTP_HOST} || (($env->{SERVER_NAME} || "") . ":" . ($env->{SERVER_PORT} || 80))) . ($env->{SCRIPT_NAME} || '/'); return $uri; } if ($0 eq __FILE__) { require Test::More; Test::More->import(); { my $req = Plack::Request::Cached->new({HTTP_HOST => 'example.com'}); is($req->uri, "http://example.com/"); $req->env->{HTTP_HOST} = 'example.jp'; is($req->uri, "http://example.jp/"); } is(scalar(keys %MAP), 0); done_testing(); require Benchmark; Benchmark->import(':all'); my $req = Plack::Request->new({HTTP_HOST => 'example.com'}); my $fast = Plack::Request::Cached->new({HTTP_HOST => 'example.com'}); cmpthese( -1, { 'orig' => sub { $req->uri; }, 'fast' => sub { $fast->uri; }, }, ); } 1;
まとめる時間がないんで、メモだけだが。
Plack::Request#uri と Plack::Request#base をよびまくると、支配的になるぐらい遅い。
これは、Plack::Request#uri も Plack::Reuqest#base も、一切キャッシュをしていないため。$req->env がかきかえられる可能性を考慮するとただしいのだが、ほとんどの場合はかきかえないとおもうし、自己責任でキャッシュできるような道筋が必要だとかんじた。
あるいは、なにかフックしてもいいのかもしらんけど。
URI->canonical は、必要ないケースも多いとおもうんで、それをオフにしたいというのもある。あれ遅い。
そもそも URI を object でもつ必要があるのかどうかという点もあって、id:kazuhookuあたりは、文字列でもてばいいじゃーん的なアレなんですが。