cgiでブラウザーのタイムアウトに対応するには?

[上に] [前に] [次に]
ひとのよし 1999/09/03(金) 15:25:51
やや重たいサーバでやや重たいperlのcgiを動かしています。
テレホタイムなどで回線が重たくなってくると、ブラウザーの方で
タイムアウトしてしまいます。このときにcgiがファイルを書き出している最中だと、
どうもファイルが空になるなど、壊れてしまうようです。
cgiでブラウザーのタイムアウトに対応するにはどうすればいいのでしょうか?

paraQ 1999/09/03(金) 15:52:19
http://www.tohoho-web.com/wwwperl3.htm#Signal
今回のケースは、これを使ってどうにかなるシロモノなんでしょうか。

まこ 1999/09/03(金) 16:25:41
フォームデータをファイルに書き出しているのでしょうか?
送信情報を全て受け取ってから、ファイルI/O等の処理を行っていると思います。
よって、ファイルが空になるというのは別の原因があるのでは?
CGIプロセス自体の完了がそんなに時間がかかるとは思えません。
CGIはちゃんと起動されているんですかね?
回線が混んでいて、そこまで辿り着いていないとか。
ファイルが壊れないような対策をしてはいかがでしょう。
バックアップを取り、失敗したらそのバックアップを反映させるとか。
なにか、的外れな事を言っていたら申し訳ありません(^_^;;)

まこ 1999/09/03(金) 16:30:08
追記です。
ファイル操作完了後、HTMLを標準出力すればCGIプロセスは完了すると思います。
その後の回線が混んでいて、ブラウザまで辿り着けないとしても、
CGIはとっくに終わっているのブラウザのでタイムアウトなんて取れないと思うのですが...
やっぱり、的外れ?

ひとのよし 1999/09/03(金) 21:08:35
あるデータをフォームより受け取って、今までのレコードを読み込み、
ソートして、その結果を書き出す、というCGIです。

このデータファイルにアクセスが集中してくると、新しくデータを登録しようとしても
サーバからの反応がありません、と出てブラウザーが接続を切ってしまいます。
データファイルはflockで保護しているのですが、どうにもデータが空になったり、
途中で終わったりというような傷害が出てしまいます。こういう具合なので
CGIが正常終了はしていないと思うのですが…

paraQさんのご指摘のページこの内容、よく解らないのですが、どういうことなのでしょうか?
    $SIG{'INT'} = $SIG{'HUP'} = $SIG{'QUIT'} = $SIG{'TERM'} = "sigexit";
    sub sigexit {
        # この部分に作業ファイル削除などの終了処理を記述する
    }

まこ 1999/09/06(月) 15:23:31
>CGIが正常終了はしていないと思うのですが…
正常終了していないということはHTMLを標準出力せずに異常終了しているという事になるのでしょうか?
エラー処理のフォローでHTMLを出力しているのなら別ですが。
でも、CGIが異常終了していれば「Server Error」ってなりそうな気がしますね。
何か強制終了(異常終了)する要因があるのではないでしょうか。
paraQさんのおっしゃる事は、その強制終了時のフォローについてです。
シグナルによる割り込みで強制終了するわけですが、
デフォルトではシグナルが発生するとプロセスの途中であってもそこで終了してしまいます。
そこで、終了してしまう前に後処理を入れる事ができます。
(作業ファイル削除、エラーメッセージ出力など)
もちろん再開する事もできますし、無視する事もできます。
シグナルはさまざまな種類がありますが、任意のシグナルを指定できます。
なんかUNIXの世界になってしまいましたが、説明下手で申し訳ありません。
CGIが正常処理をしているか調べてはいかがでしょうか?
ファイルの排他処理にもなにか問題があるのかもしれません。
たとえ、回線が混雑してサーバからのHTMLが返ってこなくても、
正常にファイル処理が行えていれば問題ないのではないでしょうか。
どなたか、他に原因が分かる方フォローお願いします。

B-Cus 1999/09/07(火) 23:46:36
> サーバからの反応がありません、と出てブラウザーが接続を切ってしまいます。
これはタイムアウトということですか?


以下、Solaris2.6/apache1.2.6 で確認した話。

ブラウザとWWWサーバの接続が切れても(中断でもブラウザ側のタイム
アウトでも)、CGIはそのまま動き続けます。でも、それだとどんどん
プロセスが増えていくので、一定時間経っても終了しないプロセスは
WWWサーバが SIGTERM で殺します。apacheのデフォルト設定ではこの
時間は300秒です。

ですから、CGI側からはブラウザがタイムアウトしたことを検知できません。
実行時間が300秒(設定次第)以上かかっているのならば、CGIの処理を
見直しましょう。そのCGIが300秒以内で終了するはずならば、シグナルは
送られてこないはずなので、ファイル内容が消えるのは、ちゃんと
排他処理していないとか、そういうアルゴリズム的な理由が考えられます。

確認方法ですが、以下のような感じでログを取ればいいでしょう。
片っ端からシグナルをブロックして、送られてきたらファイルに
書き込んで終了します。

シグナルの種類はOSによって違いますが、kill -l で確認できます。
なお、SIGKILLはブロックできません。

#!/usr/local/bin/perl
$|=1;
print "Content-type: text/plain\n\n";

($sec,$min,$hour,$day,$month,$year) = localtime(time);
$begin_time = sprintf("%4d-%02d-%02d %02d:%02d:%02d",$year+1900,$month+1,$day,$hour,$min,$sec);

$sig=<<END;
HUP INT QUIT ILL TRAP ABRT EMT FPE BUS SEGV SYS PIPE ALRM TERM USR1 USR2
PWR WINCH URG POLL STOP TSTP CONT TTIN TTOU VTALRM PROF XCPU XFSZ WAITING
LWP FREEZE THAW LOST
END

@sig = split(/[ \n]+/,$sig);
foreach ( @sig ){
 $handler = $_;
 $handler =~ s/[-\+]//;
 $a= "\$SIG{'$_'}=\\&${handler}_handler;";
 $b= "sub ${handler}_handler{&put_signal_log('$handler')}";
 eval($a);
 eval($b);
 print "$a\n";
}

sleep 1000;
exit;

sub put_signal_log {
 my ($signal) = @_;
 ($sec,$min,$hour,$day,$month,$year) = localtime(time);
 $now_time = sprintf("%4d-%02d-%02d %02d:%02d:%02d",$year+1900,$month+1,$day,$hour,$min,$sec);
 open(OUT,">> /home/yourname/signal.log");
 print OUT "$signal $begin_time $now_time \n";
 close(OUT);
 exit;
}

B-Cus 1999/09/08(水) 00:19:28
> ブラウザ側のタイムアウトでも)、CGIはそのまま動き続けます。
間違えた。

WWWサーバがCGIにSIGTERMを送り、さらにブラウザとの接続を切るので
その時点でブラウザ側のタイムアウトが発生する、ってことです。
少なくとも FreeBSD2.2.7/NetscapeNavigator4.6で試した限りでは、
ブラウザが能動的にタイムアウトすることはありませんでした。

まこ 1999/09/08(水) 11:26:26
> > ブラウザ側のタイムアウトでも)、CGIはそのまま動き続けます。

NetscapeEnterpriseServer ではCGIはそのまま動き続けました。
おかげで、CGIプロセスがたまってしまう事も。
最近ですが、ファイル排他制御がうまく行かず、デッドロックを起こし、
CGIが50個ぐらい上がったままの時がありました。(お客さんごめんなさい)
参考までに。

ひとのよし 1999/09/08(水) 12:59:23
原因が分かった気がします。
B-Cusさんのチェックプログラムを動かしてみたところ
30秒ごとに2回、TERMっていうのが送られてきて終わってました。
ということは、このサーバのSIGKILLは60秒に設定されているということでしょうか。
今までのログを見ても確かにそんな感じになってます。
これは厳しい… 現状で書き出しているデータファイル、200kbぐらい
あるのですが、何も処理せず書き戻すだけでもギリギリな時間でしょう。
データ生成とかの時間を考えるとこれは切れてしまうなぁ。
このサーバを使っているのがうちのところだけじゃないので
設定の変更は難しいので、何か別の手を考えるしかないのかなぁ…
まこさん、B-Cusさん、ありがとうございました。

まこ 1999/09/08(水) 13:14:32
SIGTERMはブロックできるとして、60秒後にはSIGKILLが飛んでくるんですか。
サーバでそんな事をしているとは、厳しいですね。
でも60秒は短いですよね。
かえってゴミファイルが残ってディスクを圧迫するような気がしますが。
サーバーの調整の域のような気がしますので、管理者に相談してはいかがでしょう。

まこ 1999/09/08(水) 13:37:49
参考程度に...

現在、私が開発しているものですが、
ファイルI/O用のデーモンを上げて、
CGIはデータをデーモンに投げます。
プロセス間通信はメッセージキューで行っています。
この場合、デーモンしかファイルをいじらないので排他の必要がありません。
これならCGIは早く終わると思います。
CGI毎の排他制御って、たくさんリクエストがあるとWAITする時間がばかになりませんものね。

でも、これは制限がかなりありますね。
デーモンなんて、簡単に上げられないかもしれないし。
perlはよく知らないのですが、Cプログラムでしかできないかもしれないし。
あくまで参考ですよ(^^)

B-Cus 1999/09/08(水) 14:59:35
僕の感覚から言うと、CGIの終了までに60秒かかるのは長過ぎだと思います。
もちろんマシンスペックや、そのときの負荷などもありますので、いちがい
には言えませんが、アルゴリズムの見直しで何とかならんですか?

CGIはリクエストを送るだけにして、cronで数分ごとにたまった
queueを処理するプログラムを実行するとか、重い部分だけ
Cで書くとか。

> 200kbぐらいあるのですが、何も処理せず書き戻すだけでもギリギリな
> 時間でしょう。
でも、200KBを読んで書くだけで60秒ということは、結構ロースペックなのかな。

forkして、親はHTML吐いて終了、子はファイルの処理を続けるというふうに
すれば、WWWサーバからのSIGTERM/SIGKILLはかわせるかもしれませんが、
あまり真っ当なやり方ではないです。

まこ 1999/09/08(水) 15:49:05
>僕の感覚から言うと、CGIの終了までに60秒かかるのは長過ぎだと思います。

そうですね。B−Cusさんのおっしゃるとおり、長すぎますね。
もし、ロースペックなら、それなりの時間を設定してほしいですよね。
perlはただでさえ、時間がかかります。
ちょっとアルゴリズムを見直しただけで5分の1ぐらいに短縮した経験があります。(それまで無駄が多すぎたのですが)
Cにするともっと早くなりました。

ひとのよし 1999/09/11(土) 04:05:31
[[解決]]
反応遅くなりましてすいません。
もちろん、単体での動作では60秒なんてかかりません。ですがアクセス集中すると
4,5件程度の同時接続は発生するようで、ファイルのロック解放を待ってる間に…という
感じみたいです。
結果的には根本的に方式を変えることにしました。
長々とお付き合いありがとうございました。勉強になりました。

[上に] [前に] [次に]