12

信頼できないサーバーへのリクエストを実装しようとしています。リクエストはあると便利ですが、perl スクリプトが正常に完了するために 100% 必要というわけではありません。問題は、サーバーがときどきデッドロックし (理由を突き止めようとしています)、要求がまったく成功しないことです。サーバーはそれがライブであると認識しているため、ソケット接続を開いたままにしておくため、LWP::UserAgent のタイムアウト値は何の役にも立ちません。リクエストに絶対タイムアウトを強制する最良の方法は何ですか?

参考までに、これは DNS の問題ではありません。デッドロックは、Postgres データベースに同時にヒットする膨大な数の更新と関係があります。テスト目的で、本質的に while(1) {} 行をサーバーの応答ハンドラに入れました。

現在、コードは次のようになっています。

my $ua = LWP::UserAgent->new;
ua->timeout(5); $ua->cookie_jar({});

my $req = HTTP::Request->new(POST => "http://$host:$port/auth/login");
$req->content_type('application/x-www-form-urlencoded');
$req->content("login[user]=$username&login[password]=$password");

# This line never returns 
$res = $ua->request($req);

シグナルを使用してタイムアウトをトリガーしようとしましたが、うまくいかないようです。

eval {
    local $SIG{ALRM} = sub { die "alarm\n" };
    alarm(1);
    $res = $ua->request($req);
    alarm(0);
};
# This never runs
print "here\n";

私が使用する最終的な回答は、誰かがオフラインで提案したものですが、ここで言及します。何らかの理由で、SigAction は機能しますが、$SIG(ALRM) は機能しません。理由はまだわかりませんが、これは動作することがテストされています。2 つの作業バージョンを次に示します。

# Takes a LWP::UserAgent, and a HTTP::Request, returns a HTTP::Request
sub ua_request_with_timeout {
    my $ua = $_[0];
    my $req = $_[1];
    # Get whatever timeout is set for LWP and use that to 
    #  enforce a maximum timeout per request in case of server
    #  deadlock. (This has happened.)
    use Sys::SigAction qw( timeout_call );
    our $res = undef;
    if( timeout_call( 5, sub {$res = $ua->request($req);}) ) {
        return HTTP::Response->new( 408 ); #408 is the HTTP timeout
    } else {
        return $res;
    }
}
sub ua_request_with_timeout2 {
    print "ua_request_with_timeout\n";
    my $ua = $_[0];
    my $req = $_[1];
    # Get whatever timeout is set for LWP and use that to 
    #  enforce a maximum timeout per request in case of server
    #  deadlock. (This has happened.)
    my $timeout_for_client = $ua->timeout() - 2;
    our $socket_has_timedout = 0;

    use POSIX;
    sigaction SIGALRM, new POSIX::SigAction(
                                            sub {
                                                $socket_has_timedout = 1;
                                                die "alarm timeout";
                                            }
                                            ) or die "Error setting SIGALRM handler: $!\n";
    my $res = undef;
    eval {
        alarm ($timeout_for_client);
        $res = $ua->request($req);
        alarm(0);
    };
    if ( $socket_has_timedout ) {
        return HTTP::Response->new( 408 ); #408 is the HTTP timeout
    } else {
        return $res;
    }
}
4

4 に答える 4

12

LWP::UserAgent のサブクラスであるLWPx::ParanoidAgentを試すこともできます。これは、リモート Web サーバーとの対話方法についてより慎重です。

特に、グローバル タイムアウトを指定できます。これは、LiveJournal プロジェクトの一環として Brad Fitzpatrick によって開発されました。

于 2008-09-16T15:08:17.590 に答える
1

次のように独自のタイムアウトを作成できます。

use LWP::UserAgent;
use IO::Pipe;

my $agent = new LWP::UserAgent;

my $finished = 0;
my $timeout = 5;

$SIG{CHLD} = sub { wait, $finished = 1 };

my $pipe = new IO::Pipe;
my $pid = fork;

if($pid == 0) {
    $pipe->writer;
    my $response = $agent->get("http://stackoverflow.com/");
    $pipe->print($response->content);
    exit;
}

$pipe->reader;

sleep($timeout);

if($finished) {
    print "Finished!\n";
    my $content = join('', $pipe->getlines);
}   
else {
    kill(9, $pid);
    print "Timed out.\n";
}   
于 2008-09-16T20:08:27.590 に答える
0

私が理解していることから、タイムアウト プロパティは DNS タイムアウトを考慮していません。DNSルックアップを個別に作成し、それが機能する場合はサーバーにリクエストを送信し、ユーザーエージェントに正しいタイムアウト値を設定することができます。

これはサーバーの DNS の問題ですか、それとも他の問題ですか?

編集: IO::Socket にも問題がある可能性があります。IO::Socket モジュールを更新してみて、それが役立つかどうかを確認してください。LWP::UserAgent のタイムアウトが機能しないバグがあったことは確かです。

アレックス

于 2008-09-16T15:06:05.787 に答える