Pregunta

Estoy intentando implementar una solicitud a un servidor no confiable.Es bueno tener la solicitud, pero no es 100% necesaria para que mi script Perl se complete correctamente.El problema es que el servidor ocasionalmente se bloquea (estamos tratando de descubrir por qué) y la solicitud nunca tendrá éxito.Dado que el servidor cree que está activo, mantiene abierta la conexión del socket, por lo que el valor de tiempo de espera de LWP::UserAgent no nos sirve de nada.¿Cuál es la mejor manera de imponer un tiempo de espera absoluto en una solicitud?

Para su información, esto no es un problema de DNS.El punto muerto tiene algo que ver con una gran cantidad de actualizaciones que llegan a nuestra base de datos de Postgres al mismo tiempo.Para propósitos de prueba, básicamente hemos puesto una línea while(1) {} en el controlador de respuesta del servidor.

Actualmente, el código tiene este aspecto:

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

Intenté usar señales para activar un tiempo de espera, pero no parece funcionar.

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

La respuesta final que voy a utilizar fue propuesta por alguien fuera de línea, pero la mencionaré aquí.Por alguna razón, SigAction funciona mientras que $SIG(ALRM) no.Todavía no estoy seguro de por qué, pero se ha probado que funciona.Aquí hay dos versiones funcionales:

# 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;
    }
}
¿Fue útil?

Solución

Podrías intentarlo LWPx::AgenteParanoico, una subclase de LWP::UserAgent que es más cautelosa acerca de cómo interactúa con servidores web remotos.

Entre otras cosas, le permite especificar un tiempo de espera global.Fue desarrollado por Brad Fitzpatrick como parte del proyecto LiveJournal.

Otros consejos

Puedes crear tu propio tiempo de espera de esta manera:

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

Por lo que tengo entendido, la propiedad de tiempo de espera no tiene en cuenta los tiempos de espera de DNS.Es posible que pueda realizar una búsqueda de DNS por separado y luego realizar la solicitud al servidor si funciona, con el valor de tiempo de espera correcto establecido para el agente de usuario.

¿Es esto un problema de DNS con el servidor o algo más?

EDITAR:También podría ser un problema con IO::Socket.Intente actualizar su módulo IO::Socket y vea si eso ayuda.Estoy bastante seguro de que había un error que impedía que funcionaran los tiempos de espera de LWP::UserAgent.

Alex

La siguiente generalización de una de las respuestas originales también restaura el controlador de señales de alarma al controlador anterior y agrega una segunda llamada a alarm(0) en caso de que la llamada en el reloj de evaluación genere una excepción que no sea de alarma y queramos cancelar la alarma.Se pueden agregar $@ inspecciones y manejo adicionales:

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;
     }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top