문제

신뢰할 수 없는 서버에 대한 요청을 구현하려고 합니다.요청은 있으면 좋지만 Perl 스크립트를 성공적으로 완료하는 데 100% 필요한 것은 아닙니다.문제는 서버가 때때로 교착 상태에 빠지고(이유를 알아내려고 노력 중임) 요청이 결코 성공하지 못한다는 것입니다.서버는 자신이 활성화되어 있다고 생각하기 때문에 소켓 연결을 열린 상태로 유지하므로 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::ParanoidAgent, 원격 웹 서버와 상호 작용하는 방식에 대해 더 신중한 LWP::UserAgent의 하위 클래스입니다.

무엇보다도 전역 시간 제한을 지정할 수 있습니다.LiveJournal 프로젝트의 일부로 Brad Fitzpatrick이 개발했습니다.

다른 팁

다음과 같이 시간 초과를 직접 설정할 수 있습니다.

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

내가 이해한 바에 따르면 시간 초과 속성은 DNS 시간 초과를 고려하지 않습니다.별도로 DNS 조회를 수행한 다음 작동하는 경우 사용자 에이전트에 대해 올바른 시간 초과 값을 설정하여 서버에 요청할 수 있습니다.

서버의 DNS 문제인가요, 아니면 다른 문제인가요?

편집하다:IO::Socket에 문제가 있을 수도 있습니다.IO::Socket 모듈을 업데이트해 보고 도움이 되는지 확인하세요.나는 LWP::UserAgent 시간 초과가 작동하지 못하게 하는 버그가 있다고 확신합니다.

알렉스

원래 답변 중 하나에 대한 다음 일반화는 알람 신호 핸들러를 이전 핸들러로 복원하고 평가 시계의 호출이 알람이 아닌 예외를 발생시키고 알람을 취소하려는 경우에 대한 두 번째 호출을 추가합니다.추가 $@ 검사 및 처리가 추가될 수 있습니다.

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