2008-09-16 9 views
10

Ich versuche, eine Anfrage an einen unzuverlässigen Server zu implementieren. Die Anfrage ist eine nette Sache, aber nicht 100% erforderlich, damit mein Perl-Skript erfolgreich abgeschlossen werden kann. Das Problem ist, dass der Server gelegentlich blockiert (wir versuchen herauszufinden, warum) und die Anfrage wird niemals erfolgreich sein. Da der Server denkt, dass es live ist, hält es die Socket-Verbindung offen, so dass der Timeout-Wert von LWP :: UserAgent uns nicht gut tut. Was ist der beste Weg, um eine absolute Zeitüberschreitung bei einer Anfrage zu erzwingen?True Timeout auf LWP :: UserAgent Anfrage Methode

FYI, das ist kein DNS-Problem. Der Deadlock hat etwas mit einer großen Anzahl von Updates zu tun, die gleichzeitig unsere Postgres-Datenbank betreffen. Zu Testzwecken haben wir im Server-Antworthandler im Wesentlichen eine while (1) {} -Zeile gesetzt.

Derzeit sieht der Code wie folgt:

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 versucht, Signale mit einem Timeout auslösen, aber das scheint nicht zu funktionieren.

Die endgültige Antwort, die ich verwenden werde, wurde von jemandem offline vorgeschlagen, aber ich werde es hier erwähnen. Aus irgendeinem Grund funktioniert SigAction, während $ SIG (ALRM) nicht funktioniert. Immer noch nicht sicher, warum, aber das wurde getestet, um zu funktionieren. 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; 
    } 
} 
+0

Mögliche Duplikat [Wie eine bestimmte Timeout in Perl erzwingen?] (Http://stackoverflow.com/questions/15899855/how-to-enforce-a -definite-timeout-in-perl) – sixtyfootersdude

Antwort

12

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

Unter anderem können Sie ein globales Timeout festlegen. Es wurde von Brad Fitzpatrick im Rahmen des LiveJournal-Projekts entwickelt.

+0

Diese Zeitüberschreitung ist immer noch vom DNS-Timeout betroffen – ryansstack

0

Von dem, was ich verstehe, die Timeout-Eigenschaft nimmt nicht berücksichtigt DNS-Timeouts. Es ist möglich, dass Sie eine DNS-Suche separat durchführen und dann die Anfrage an den Server senden können, falls dies funktioniert, mit dem richtigen Zeitüberschreitungswert für das Useragent.

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 Sie, ob das hilft. Ich bin mir ziemlich sicher, dass da ein Bug war, der verhinderte, dass LWP :: UserAgent Timeouts funktionierten.

Alex

1

Sie können Ihre eigenen Timeout wie folgt machen:

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

Es nervt mich immer, wenn Leute 'beitreten ', <$fh>' - es ist unnötig busywork, die Eingabe in Zeilen aufzuteilen und dann diese wieder zusammenzufügen, Außerdem braucht es doppelt so viel Speicher. Schreibe 'do {local $ /; <$fh>} 'stattdessen. –

+2

Sie haben Recht, aber wenn ich Module verwende, weiß ich nicht genau, was es im Inneren macht. Ich verwende lieber die Methoden, die es bietet. IO :: Pipe könnte $/auf einen anderen Wert setzen (offensichtlich nicht, aber es könnte). Auch das "Do" ist an dieser Stelle unnötig. Klammern reichen aus, um einen neuen Bereich zu starten. – jkramer

0

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) in Fall, der Aufruf in der Eval-Uhr löst eine Nicht-Alarm-Ausnahme aus und wir wollen den Alarm abbrechen. 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; 
    } 
}