HOME備忘帳

処理待ちでブラウザがタイムアウトするのを防ぐ

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

[ ページ先頭へ ]