Domanda

Sto cercando di implementare una richiesta a un server inaffidabili.La richiesta è bello avere, ma non al 100%, necessaria per il mio script in perl per completare con successo.Il problema è che il server a volte deadlock (stiamo cercando di capire perché) e la richiesta non potrà mai avere successo.Poiché il server pensa che sia live, mantiene la connessione socket aperto così LWP::UserAgent del valore di timeout non buona che-così-mai.Qual è il modo migliore per applicare un timeout assoluto su richiesta?

FYI, questo non è un problema di DNS.La situazione di stallo ha qualcosa a che fare con un enorme numero di aggiornamenti di colpire il nostro database Postgres allo stesso tempo.Per scopi di test, abbiamo essenzialmente mettere un while(1) {} riga nel server del gestore di risposte.

Attualmente, il codice si presenta in questo modo:

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

Ho provato con i segnali di trigger di un timeout, ma che non sembra funzionare.

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

La risposta finale ho intenzione di utilizzare è stato proposto da qualcuno in linea, ma io ne parla qui.Per qualche ragione, SigAction funziona mentre $(SIG ALRM) non.Ancora non so perché, ma questo è stato testato per funzionare.Qui ci sono due versioni:

# 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;
    }
}
È stato utile?

Soluzione

Si potrebbe provare LWPx::ParanoidAgent, una sottoclasse di LWP::UserAgent che è più prudente circa il modo in cui interagisce con il server web remoto.

Tra le altre cose, permette di specificare un timeout globale.Esso è stato sviluppato da Brad Fitzpatrick come parte di LiveJournal progetto.

Altri suggerimenti

Potete fare il vostro proprio timeout come questo:

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

Da quello che ho capito, la proprietà timeout e non tiene conto del timeout DNS.È possibile che si potrebbe fare una ricerca DNS separatamente, poi fare la richiesta al server se funziona, con il corretto valore di timeout impostato per l'useragent.

È questo un problema di DNS con il server, o qualcos'altro?

EDIT:Potrebbe anche essere un problema con IO::Socket.Prova ad aggiornare il tuo IO::Socket modulo, e vedere se questo aiuta.Sono abbastanza sicuro che c'era un bug che impediva LWP::UserAgent timeout da lavoro.

Alex

La seguente generalizzazione di una delle risposte che ripristina anche il segnale di allarme gestore e il gestore precedente e aggiunge una seconda chiamata di allarme(0) nel caso in cui la chiamata in eval orologio lancia un allarme non fa eccezione e si desidera annullare l'allarme.Ulteriore $@ ispezione e di gestione possono essere aggiunti:

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;
     }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top