処理待ちでブラウザがタイムアウトするのを防ぐ
CGIで、処理はバックで走らせつつ、ブラウザには先に返答を返してしまいたいとき、どうするかという話です。
forkを使って別プロセスを作る方法を良く見かけるのですが、ラクダ本を見る限りでは、ゾンビとかややこしそうな臭いが...。
ということで別の案を検討してみました。
forkを使って別プロセスを作る。
まずは、よく見かける方法。親プロセスは、「処理開始」をブラウザに知らせて終了します。子プロセスは、実際の処理を行います。
問題点
親プロセスが子プロセスの処理終了を待たずにexitしてしまうと、子プロセスがゾンビになってしまう。
fork以外の方法を使って別プロセスをスタートさせた場合には、Perlが、ゾンビになった子プロセスの後始末をしてくれる。しかし、じかにforkを使って別プロセスを起動した場合には、あなたは自分で後始末をしなければならない。 (ラクダ本「16.1.2 ゾンビの後始末をする」)
解決案
$SIG{CHILD} = "IGNORE"; で解決するのが簡単だが、すべてのカーネルでは有効ではない。
自分で後始末をするには、ループでゾンビがいなくなるまでwait(waitpid)するしかないらしい。
ラクダ本「fork」の項目にあったサンプルを元に:
use Errno qw(EAGAIN);
$SIG{CHILD} = 'IGNORE'; # 有効であってほしい
FORK:
{
if ($pid = fork) {
# ブラウザに何か返す。
print <<EOM;
Content-type: text/html
<html>
<head><title>test</title></head>
<body>Hello, world!</body>
</html>
EOM
}
elsif (defined $pid) {
# 仮に15秒かかる処理をしたとして...
sleep 15;
# 処理終了後にログを書く
open FH, '>', './testlog.txt';
print FH 'test';
close FH;
exit;
}
elsif ($! == EAGAIN) {
# リトライ
sleep 5;
redo FORK;
}
else {
# fork失敗!
print <<EOM;
Content-type: text/html
<html>
<head><title>test</title></head>
<body>ERROR<br>$!</body>
</html>
EOM
}
}
exit;
ブラウザのメッセージはほぼ即時表示され、15秒後にテストログファイルが作成された(FTPで確認)。
[ ページ先頭へ ]
とにかくブラウザに何かを返せばよい?
問題は、「処理に時間がかかること」ではなく、「ブラウザを待たせている」ということ。
なんとかして、プログラムの途中で、ブラウザに「これ以上待たなくていいよ」と明確に知らせられれば。 返答(たいがいはHTMLソース)を最後まで返してしまった後なら、同じプログラムが引き続き時間のかかる処理を実行していても、問題ないはず。
問題点
普通は、プログラムの処理がすべて終了しないと、結果は表示されない。 pushのことが頭をよぎったけど、そんな大層なことはしたくない。
$| = 1;では、うまくいかない。
(一応)解決
プログラム終了に伴う標準出力のcloseが重要だったようで。
以下で解決。
print <<EOM; Content-type: text/html <html> <head><title>test</title></head> <body>Hello, world!</body> </html> EOM # ブラウザへの出力はこれで全部 close STDOUT; # 15秒かかる処理をして... sleep 15; # ログを出力 open FH, '>', './testlog.txt'; print FH 'test'; close FH; exit;
レンタルサーバーなので、ゾンビがどうとか、考えるだに気持ち悪いので、こっちを採用。
※件の時間がかかる処理が、ちゃんと終わったのかどうか、ブラウザから利用しているときは判断がつかない。 ログファイルを書くとか、最後に完了メールで知らせるとか、そういうことは必要かと。
最終更新日:2010/05/07
[ ページ先頭へ ]