Frage

Ich versuche, eine Anfrage zu einem unzuverlässigen Server zu implementieren. Die Anfrage ist ein schön zu haben, aber nicht 100% für meine Perl-Skript erforderlich erfolgreich abgeschlossen. Das Problem ist, dass der Server behält sich das Recht Deadlock (wir versuchen, herauszufinden, warum) und die Anforderung wird nie gelingen. Da der Server denkt, dass es aktiv ist, hält es die Socket-Verbindung geöffnet somit LWP :: Useragent der Timeout-Wert uns tut nicht gut, was so überhaupt. Was ist der beste Weg, um eine absolute Timeout auf eine Anforderung zu erzwingen?

FYI, ist dies kein DNS-Problem. Die Blockade hat etwas mit einer massiven Anzahl von Updates schlägt unsere Postgres-Datenbank zur gleichen Zeit zu tun. Zu Testzwecken haben wir im Wesentlichen eine while (1) {} Zeile in den Server-Antwort-Handler setzen.

Zur Zeit der Code sieht so aus:

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);

Ich habe unter Verwendung von Signalen versucht, ein Timeout auslösen, aber das scheint nicht zu funktionieren.

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

Die letzte Antwort, die ich verwenden werde wurde offline von jemandem vorgeschlagen, aber ich werde es hier erwähnen. Aus irgendeinem Grund funktioniert sigaction während $ SIG (ALRM) nicht. Immer noch nicht sicher, warum, aber dies wird getestet zu arbeiten. Hier sind zwei Arbeitsvarianten:

# 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;
    }
}
War es hilfreich?

Lösung

Sie könnten versuchen, LWPx :: ParanoidAgent , eine Unterklasse von LWP :: Useragent, die vorsichtiger ist, wie es in Wechselwirkung mit Remote-Web-Servern.

Unter anderem ermöglicht es Ihnen, eine globale Timeout angeben. Es wurde von Brad Fitzpatrick als Teil des Livejournal-Projekt entwickelt.

Andere Tipps

Sie können Ihre eigenen Timeout wie folgt vor:

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";
}   

Von dem, was ich verstehe, die Timeout-Eigenschaft nicht berücksichtigt nicht den DNS-Timeouts. Es ist möglich, dass Sie separat ein DNS-Lookup machen könnten, dann die Anfrage an den Server, ob das funktioniert, mit dem richtigen Zeitüberschreitungswert für die User-Agenten festgelegt.

Ist das ein DNS-Problem mit dem Server, oder etwas anderes?

EDIT: Es könnte auch ein Problem mit IO :: Socket sein. Versuchen Sie Ihr IO :: Socket-Modul zu aktualisieren, und sehen, ob das hilft. Ich bin mir ziemlich sicher, dass es ein Fehler in es war, das war LWP verhindern :: Useragent-Timeouts zu arbeiten.

Alex

Die folgende Verallgemeinerung von einem der ursprünglichen Antworten auch wieder das Alarmsignal Handler zum vorherigen Handler und fügt einen zweiten Anruf zu alarmieren (0), falls der Anruf in der eval Uhr wirft eine nicht Alarm Ausnahme, und wir wollen abbrechen der Alarm. Weitere $ @ Inspektion und Handhabung können hinzugefügt werden:

sub ua_request_with_timeout {
    my $ua = $_[0];
    my $request = $_[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.)`enter code here`
    my $timeout_for_client_sec = $ua->timeout();
    our $res_has_timedout = 0; 

    use POSIX ':signal_h';

    my $newaction = POSIX::SigAction->new(
        sub { $res_has_timedout = 1; die "web request timeout"; },# the handler code ref
        POSIX::SigSet->new(SIGALRM),
        # not using (perl 5.8.2 and later) 'safe' switch or sa_flags
    );  

    my $oldaction = POSIX::SigAction->new();
    if(!sigaction(SIGALRM, $newaction, $oldaction)) {
        log('warn',"Error setting SIGALRM handler: $!");
        return $ua->request($request);
    }   

    my $response = undef;
    eval {
        alarm ($timeout_for_client_sec);
        $response = $ua->request($request);
        alarm(0);
    }; 

    alarm(0);# cancel alarm (if eval failed because of non alarm cause)
    if(!sigaction(SIGALRM, $oldaction )) {
        log('warn', "Error resetting SIGALRM handler: $!");
    }; 

    if ( $res_has_timedout ) {
        log('warn', "Timeout($timeout_for_client_sec sec) while waiting for a response from cred central");
        return HTTP::Response->new(408); #408 is the HTTP timeout
    } else {
        return $response;
     }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top