Истинное время ожидания для метода запроса LWP::UserAgent

StackOverflow https://stackoverflow.com/questions/73308

  •  09-06-2019
  •  | 
  •  

Вопрос

Я пытаюсь реализовать запрос к ненадежному серверу.Запрос приятный, но не на 100% необходимый для успешного завершения моего perl-скрипта.Проблема в том, что сервер иногда переходит в тупиковую ситуацию (мы пытаемся выяснить, почему), и запрос никогда не будет выполнен успешно.Поскольку сервер считает, что он работает, он сохраняет соединение с сокетом открытым, таким образом, значение тайм-аута LWP::UserAgent не приносит нам никакой пользы.Каков наилучший способ принудительно установить абсолютный тайм-аут для запроса?

К вашему сведению, это не проблема DNS.Тупиковая ситуация как-то связана с огромным количеством обновлений, поступающих в нашу базу данных Postgres одновременно.В целях тестирования мы, по сути, поместили строку while(1) {} в обработчик ответа сервера.

В настоящее время код выглядит следующим образом:

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

Я пробовал использовать сигналы для запуска тайм-аута, но, похоже, это не работает.

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

Окончательный ответ, который я собираюсь использовать, был предложен кем-то в автономном режиме, но я упомяну его здесь.По какой-то причине SigAction работает, в то время как $SIG(ALRM) - нет.Все еще не уверен, почему, но это было протестировано на работоспособность.Вот две рабочие версии:

# 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;
    }
}
Это было полезно?

Решение

Вы могли бы попробовать LWPx::ПараноидАгент, подкласс LWP::UserAgent, который более осторожно относится к тому, как он взаимодействует с удаленными веб-серверами.

Помимо прочего, он позволяет вам указать глобальный тайм-аут.Он был разработан Брэдом Фитцпатриком в рамках проекта LiveJournal.

Другие советы

Вы можете установить свой собственный тайм-аут следующим образом:

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

Насколько я понимаю, свойство timeout не учитывает тайм-ауты DNS.Возможно, вы могли бы выполнить поиск DNS отдельно, а затем отправить запрос на сервер, если это сработает, с правильным значением тайм-аута, установленным для useragent.

Это проблема DNS с сервером или что-то еще?

Редактировать:Это также может быть проблема с IO::Socket.Попробуйте обновить свой модуль ввода-вывода :: Socket и посмотрите, поможет ли это.Я почти уверен, что там была ошибка, которая не позволяла работать тайм-аутам LWP:: UserAgent.

Алекс

Следующее обобщение одного из исходных ответов также восстанавливает обработчик сигнала тревоги к предыдущему обработчику и добавляет второй вызов к alarm(0) на случай, если вызов в eval clock выдает исключение, отличное от alarm, и мы хотим отменить сигнал тревоги.Могут быть добавлены дополнительные проверки $@ и обработка:

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;
     }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top