SpeedyCGI と module reload
SpeedyCGI で遊んでました。mod_perl ほど設定に煩わされることもないし,ライブラリパスの局所化も容易なんで実験用としてはなかなか楽しいです。
ですけど,
ありがち。でもそれめんどくさいよねぇ。
ということで,Apache2::Reload みたいなのを作ろうと思っていろいろ試していたんですが一筋縄ではいきませんでした。
どうも SpeedyCGI の処理の流れが(コードの分析からじゃなくて現象からの分析なので適当)
- フロントエンド(mod_speedycgi や speedy)が CGI スクリプトを起動しようとする
- もし下記の条件にあてはまればバックエンドを再利用する
- さもなければバックエンドを新規作成する
- 作成されたばかりのバックエンドは CGI スクリプトをパースする
- バックエンドは子を fork し
- 子はインタプリタ処理を開始し,結果をフロントエンドに投げる
形になってるんで CGI::SpeedyCGI->shutdown_now() しても 6 の子が死んで4 のバックエンドが fork するだけなのです。つまり,この時点でスクリプトの再パースは行われない,と。
ということで ModPerl::Util::unload_package_pp(←これはこれでなかなかの力技)を参考に書いてみてもうまくいかない。
で,結果的にどうしたかというと
- %INC のパッケージが更新されていなければスルー
- 「リロードしてね」というメッセージを出力する
- getppid してバックエンド親プロセス(↑でいう 4)を kill TERM する
- 自分は CGI::SpeedyCGI->shutdown_now する
というシンプル?な形でうまくいきました。子から親に氏ねというのもあれですがうまく動いたのでよしとします。
コンセプトモデルとしては
package SCGI::FakeReload; use strict; use CGI::SpeedyCGI; my %mtime; sub _modified { while (my ($pkg, $file) = each %INC) { my $mt = (stat $file)[9]; if (exists $mtime{$pkg} && $mtime{$pkg} > 0 && $mt > $mtime{$pkg}) { return 1; } $mtime{$pkg} = $mt; } return 0; } sub check { if (_modified()) { print "Content-Type: text/plain\n\nReload !\n"; kill 15, getppid; CGI::SpeedyCGI->shutdown_now(); # NOTREACHED } } 1;
use SCGI::FakeReload; SCGI::FakeReload::check();
って書いとけよみたいな感じですが,いちいち check() 実行するのもうざいんで,私家版では手を加えて require するだけで済むパッケージにしてます。