ModPerl::RegistryとModPerl::PerlRunの違いを実現してる実装を読んだ

mod_perl*1を使う際、PerlResponseHandler*2に、
ModPerl::RegistoryとModPerl::PerlRunのどちらを使うか選択肢が発生します*3

ModPerl::RegistryとModPerl::PerlRunの違い

両者における明確な違いは以下の2点です。

これを検証するために、BEGINやCHECK、INITブロックにprint文を入れ込んで、
ModPerl::RegistryとModPerl::PerlRunで挙動がどう異なるのか調べる作業を
やったことがある方もいらっしゃるかと思います。

かく言う自分も「mod_perlの実験(2) - Perlプログラムのライフサイクル」を読みながら、
ハンドラーの違いで評価の作法がどう異なるのかを検証をしたことがあります。

で、ModPerl::RegistryとModPerl::PerlRunのこれらの挙動の違いは、
XSレベルで実現されているのだとずっと思っていたのですが、
意外にもPerlレベルで、ModPerl::RegistryCookerというモジュール実装されていました。

この土日にその辺のことを勉強したので、以下で説明してみます。

違いを実現している実装部

まず、ModPerl::RegistryとModPerl::PerlRunは、
ModPerl::RegistryCookerというモジュールを継承して出来ています。
ModPerl::RegistryとModPerl::PerlRunの中には実際、大した内容は書かれていなくて、
ほとんどの実装はModPerl::RegistryCookerが持っています。

ということで、以下ではほとんどModPerl::RegistryCookerの説明を書いていきます。

mod_perlではブラウザからスクリプトがリクエストされると、
ModPerl::RegistryCooker::convert_script_to_compiled_handlerの中で、
スクリプトのファイルパスを利用してユニークなパッケージ名を生成し、作ったパッケージ名で、
スクリプトの元処理を動的に以下のようにラップします*4。そして、それをevalします。

package ModPerl::ROOT::ModPerl::PerlRun::opt_local_apache2_htdocs_perl_test_2epl;

sub handler {
    local $0 = '/opt/local/apache2/htdocs/perl/perlrun.pl';

    #line 1 /opt/local/apache2/htdocs/perl/perlrun.pl
    #!/opt/local/bin/perl

    # 元々の処理がsub handler内に組み込まれる
    use strict;
    use warnings;
    use CGI;
    ・
    ・
    ・
}

さて上記の処理で、リクエストされたスクリプト
packageとして評価(コンパイル)されたことになります。

冒頭で書いたModPerl::RegistryとModPerl::PerlRunの違いは
この評価処理を1度のみとするか、リクエストの度に行うかの違いです。

2回目以降のリクエストを処理する際に、再び評価(コンパイル)すべきなのかどうか分岐する処理は、
ModPerl::RegistryCooker::default_handler()の冒頭で行われます。

具体的には、if($self->should_compile)という箇所なのですが、
ModPerl::PerlRunではこのif文が絶対に真になるように実装されていました。

ModPerl::Registryでは、ModPerl::Registry::cache_tableという、
評価済か否かの情報を保持する機構を用いて、
評価済であれば偽に、未評価であれば真になる仕組みになっていました。

sub default_handler {
    my $self = shift;

    $self->make_namespace;

    # ModPerl::PerlRunでは、絶対このif文で真になる
    if ($self->should_compile) {
        #
        # うまい説明の仕方が分からないのですが、
        # $self->should_compileにはコードリファレンスがあてがわれていて、
        # ModPerl::PerlRunでは、use constant TRUE => 1の結果が返ります。
        #
        my $rc = $self->can_compile;
        return $rc unless $rc == Apache2::Const::OK;
        $rc = $self->convert_script_to_compiled_handler;
        return $rc unless $rc == Apache2::Const::OK;
    }
# 以下省略

以上、ModPerl::RegistryとModPerl::PerlRunの評価作法の違いは、if分岐のコントロールによって成り立っていました。

余談になりますが、一般にはModPerl::RegistryとModPerl::PerlRunでは、
速度はModPerl::Registryのほうが速いと言われます。
この速度差は、前述したevalが毎回発生するかどうかのコスト差によるもののようです。

最後に

冒頭に記載しなかったのですが、実はもうひとつ、実行フェーズで行われた
requireの扱いもModPerl::RegistryとModPerl::PerlRunで異なっていて、
それも土日に勉強したのですがそれはまた別エントリで書こうと思います。

*1:ちなみにmod_perl2です

*2:ApacheのResponseフェーズで呼び出されるPerlモジュールを指定するディレクティブ

*3:実際には他にも選択肢はあるし、自分でオリジナルのハンドラを実装することも出来ます

*4:/opt/local/apache2/htdocs/perl/に置かれたtest.plというスクリプトで行った場合の例です