2008-09-16 13 views
10

Próbuję zaimplementować żądanie do niewiarygodnego serwera. Żądanie jest przyjemne, ale nie w 100% wymagane do pomyślnego ukończenia mojego skryptu Perla. Problem polega na tym, że serwer czasami się zakleszcza (próbujemy ustalić przyczynę), a prośba nigdy się nie powiedzie. Ponieważ serwer uważa, że ​​jest aktywny, utrzymuje połączenie z gniazdem, a zatem wartość limitu czasu LWP :: UserAgent nic nam nie daje. Jaki jest najlepszy sposób na wymuszenie całkowitego limitu czasu na żądanie?True timeout na LWP :: UserAgent request method

FYI, to nie jest problem z DNS. Zakleszczenie ma coś wspólnego z ogromną liczbą aktualizacji trafiających do naszej bazy danych Postgres w tym samym czasie. Do celów testowych zasadniczo wstawiliśmy (1) linię {} do obsługi odpowiedzi serwera.

Obecnie kod wygląda tak:

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

Próbowałem przy użyciu sygnałów do wyzwalania limit czasu, ale to nie wydają się działać.

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

Ostateczna odpowiedź, której zamierzam użyć, została zaproponowana przez kogoś offline, ale wspomnę o tym tutaj. Z jakiegoś powodu SigAction działa, gdy $ SIG (ALRM) nie działa. Nadal nie wiadomo dlaczego, ale zostało to przetestowane pod kątem działania. Oto dwie wersje robocze:

# 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

Możliwy duplikat [Jak wymusić określony limit czasu w Perl?] (Http://stackoverflow.com/questions/15899855/how-to-enforce-a -definite-timeout-in-perl) – sixtyfootersdude

Odpowiedz

12

Można spróbować LWPx::ParanoidAgent, podklasą LWP :: UserAgent, który jest bardziej ostrożny w jaki sposób oddziałuje z odległych serwerach.

Między innymi, pozwala określić globalny limit czasu. Został opracowany przez Brada Fitzpatricka w ramach projektu LiveJournal.

+0

Ten limit czasu nadal ma wpływ na czas oczekiwania DNS – ryansstack

0

Z tego co rozumiem, właściwość limitu czasu nie uwzględnia limitów czasu DNS. Możliwe, że możesz osobno wykonać wyszukiwanie DNS, a następnie wysłać żądanie do serwera, jeśli to działa, z poprawną wartością limitu czasu ustawioną dla useragenta.

Czy to jest problem z serwerem DNS czy coś innego?

EDYCJA: Może to być również problem z IO :: Socket. Spróbuj zaktualizować moduł IO :: Socket i sprawdź, czy to pomaga. Jestem pewien, że był tam błąd, który uniemożliwiał działanie LWP :: UserAgent.

Alex

1

Można zrobić własny limit czasu tak:

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

Zawsze powoduje błędy, gdy ludzie "dołączają", <$fh> "- niepotrzebne jest dzielenie danych wejściowych na linie, a następnie łączenie ich z powrotem, plus zajmuje dwa razy więcej pamięci. Napisz 'do {local $ /; <$fh>} 'zamiast tego. –

+2

Masz rację, ale podczas korzystania z modułów nie wiem dokładnie, co robi się w środku, wolę używać metod, które oferuje. IO :: Pipe może ustawić $/na inną wartość wewnątrz (oczywiście nie, ale może). Również "do" nie jest w tym momencie konieczne. Szelki są wystarczające do rozpoczęcia nowego zakresu. – jkramer

0

Poniższy uogólnienie jednego z oryginalnych odpowiedzi przywraca również obsługi sygnału alarmowego do poprzedniego obsługi i dodaje drugi wywołanie alarmu (0) w Przypuśćmy, że wywołanie w zegarze eval rzuca wyjątek bez alarmu i chcemy anulować alarm. Dalsza kontrola i manipulacja $ @ można dodać:

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; 
    } 
} 
Powiązane problemy