mod_perlでサーバによるシャットダウン時に後始末用の関数を呼び出すようにする。


といっても、オブジェクト指向で書いて、DESTROYを呼び出すようにするだけなんですが。server_shutdown_cleanup_registerとかなんかいろいろありますが、
外部モジュールをあまり叩きたくないので、この方法は使わないで、
自分の作ったモジュールだけでどうにかしてみます。
考え方はSpeedyCGI等でも同じなので、流用はできるはず。




オブジェクト指向における、DESTROYは、オブジェクトが破壊されたときに呼び出されます。
破壊される主な条件は以下です。

  • 別の値が代入されたり、破棄されたりして、オブジェクトが参照できなくなった。

つまりスコープから外れないかつ、オブジェクトに新しい値を代入させなければいいわけです。


今回はmod_perlで動かすことを考えます。
mod_perlでもspeedyCGIでも、ourで宣言することで、
スコープから外れても変数を破棄されなくすることができます。
そしてオブジェクトの生成は初回の1回のみでいいので、
代入なしでの宣言時は必ず未定義値になるのを利用し、definedで判定します。

test.pl
#!/usr/bin/perl -w

use Carp;
use strict;
use MyModule;

our $obj;

if(not defined $obj){
	$obj = new MyModule2("aaaa");
}

$obj->run();
MyModule.pm
package MyModule;

sub new{
	my $self = shift;
	return bless({value=> shift}, $self);
}

sub run{
	my $self = shift;
	print "Content-type: text/html\n\n";
	print "value: $self->{value} ( \$self: $self )<br>\n";
}


DESTROY{
	my $self =shift;
	open(my $FP, ">test.destroy.txt");
	printf $FP ("%s 後始末処理 ( @_ )\n", scalar(localtime(time)));
	close($FP);
}
1;


mod_perlでpreforkで動かさない場合は、カレントディレクトリに注意です。
自作モジュールと同じ階層にあっても、カレントディレクトリの位置によっては、モジュールが呼び出せません。
ただ1度読み込めば後はモジュールの情報はメモリへキャッシュされるので、
初回のBEGIN時に@INCにそのプログラムまでのフルパスをpushすれば問題ありません。
ただ、モジュールの更新は反映されない点に注意です。

BEGIN{
	our $CURRENT_DIR;
	if(exists $ENV{MOD_PERL}){
		($CURRENT_DIR = substr($ENV{SCRIPT_FILENAME}, 0, rindex($ENV{SCRIPT_FILENAME}, "/"))) or exit(0);
		unshift (@INC, $CURRENT_DIR);
	}
}


test.plを実行した後、サーバのシャットダウンをすると、
同じ階層にtest.destroy.txtが生成されるはずです。


あとはmymodule.pmのコンストラクタ(newメソッド)に値を保持したいデータの読み込み、
runメソッドにやりたいこと、DESTROYにサーバシャットダウン時の処理を書いていけばいいわけです。


これが何で使えるかといえば、簡単な例を挙げるとカウンターですね。カウンターの処理は、

  1. カウント情報読み取り
  2. カウント処理
  3. カウント情報書き出し

カウントするたびにファイルをメモリへ読み込む作業は無駄なので、
カウンタ起動時とサーバ終了時に1度づつ読み書きすればその分の無駄が減ります。


この仕組みで注意したいのは、サーバークラッシュ時です。
停電などでサーバのシャットダウン操作が正しく行われないで終了してしまった場合、
メモリのデータは大抵は消えてしまいます。


もし銀行のシステムがこんなものでは、
入金してたのに、システムがダウンして、入金が無駄になった、
という事態が起こりかねません。


カウンタのデータ程度であれば問題ないですが、必要なデータは書き出すようにするべきです。
perlには便利な関数が多いので手間にはならないでしょう。


後でカウンターを作ってみよう。