処理待ちでブラウザがタイムアウトするのを防ぐ
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
[ ページ先頭へ ]