北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

ModPerl::Registryで出会った魔のコード

mod_perlを5年以上触ってて初めてこんな現象に出会いました。ほんとひどい罠です。

現象

  • 妙なタイミングで500エラーとなる
    • Apacheを再起動すると直る
    • index.plをtouchすると500エラーとなる
    • perl -cw でチェックしても、warningも何も出ない

コード

今回問題となったindex.plはこんな感じです。どこがまずいかわかりますか?

use strict;
use warnings;
use Apache2::RequestUtil ();
use ModPerl::Util;

sub main(){
        my $r = Apache2::RequestUtil->request();
        $r->content_type('text/plain');
        print_hello($r);
        ModPerl::Util::exit();
}

sub print_hello(){
        my $r = shift;
        $r->print("It's bug!\n");
}

main();

理由

print_helloはプロトタイプが指定されており、引数がないことになっています。しかし、main()の中では引数付きで呼ばれているのでエラーとなります。これが原因。

でも、これだけじゃあ納得できない現象も起こってますね。それは以下の理由で起こります。

なんでperl -cw で syntax OK なの??

プロトタイプのチェックはコンパイル時に行われます。が、このコードだとprint_helloのプロトタイプが宣言される前に使用しているため、このチェックが行われないわけです。よって、コンパイルエラーにはなりません。

なんでtouchでエラーが起こり始めるの?

touchすると、スクリプトの更新時間が変わるため、ModPerl::Registryはスクリプトを再コンパイルしようとします。しかし、この時パッケージ内で定義されている名前を奇麗にしたりしません。元の環境を残したまま再コンパイルが行われます。そのため、最初のコンパイルで定義された「print_helloは引数がない」と言うプロトタイプ情報が有効になった状態でコンパイルが行われるため、ここで引数の型チェックが有効となってエラーとなるわけです。

error_logにはこう残ります。

[Sat Apr 19 00:13:39 2008] [error] Too many arguments for ModPerl::ROOT::ModPerl::Registry::XXXXX_index_2epl::print_hello at /XXXXX/index.pl line 9, near "$r)"\n

対策

ダミアン先生がPerl Best Practiceで言っているように*1、プロトタイプは使わない方がいいってことでしょう。個人的には好きなんですけどね、プロトタイプ。

合わせて読みたい

せっかくなので、mod_perlの一癖も二癖もある挙動と戦った過去をまとめてみました。

雑誌連載の話があれば8回分は持ちそうですね≧(´▽`)≦。

*1:"Don't use subroutine prototypes ."