Perl: How to get IO::Socket::INET timeout after X seconds?
Asked Answered
P

2

7

I'm trying to connect to some host, using invalid port, and i want to get timeout after X seconds. How to do that ?

My code:

 $sock = new IO::Socket::INET(
                  PeerAddr => $_[0],
    PeerPort => $_[1],
    Proto => 'tcp',
    Timeout => 2
    );
Polyandry answered 25/8, 2010 at 21:38 Comment(0)
D
19

If you check the code you'll see (I copied it from my Ubuntu 10.04) :

        my $timeout = ${*$sock}{'io_socket_timeout'};
#       my $before = time() if $timeout;

        undef $@;
        if ($sock->connect(pack_sockaddr_in($rport, $raddr))) {
#            ${*$sock}{'io_socket_timeout'} = $timeout;
            return $sock;
        }

        return _error($sock, $!, $@ || "Timeout")
            unless @raddr;

#       if ($timeout) {
#           my $new_timeout = $timeout - (time() - $before);
#           return _error($sock,
#                         (exists(&Errno::ETIMEDOUT) ? Errno::ETIMEDOUT() : $EINVAL),
#                         "Timeout") if $new_timeout <= 0;
#           ${*$sock}{'io_socket_timeout'} = $new_timeout;
#        }

Apparently the timeout stuff is commented out so that expleins why it is ignored.

I found a post dating from 2003 where this was discussed. One suggestion (at the bottom) was to open the socket in an eval block which gets terminated by an alarm signal :

eval { 
  local $SIG{ALRM} = sub { die 'Timed Out'; }; 
  alarm 3; 
  my $sock = IO::Socket::INET->new( 
    PeerAddr => inet_ntoa( gethostbyname($host) ), 
    PeerPort => 'whois', 
    Proto => 'tcp', 
    ## timeout => , 
  );
  $sock->autoflush;   
  print $sock "$qry\015\012"; 
  undef $/; $data = <$sock>; $/ = "\n"; 
  alarm 0; 
}; 
alarm 0; # race condition protection 
return "Error: timeout." if ( $@ && $@ =~ /Timed Out/ ); 
return "Error: Eval corrupted: $@" if $@; 

Not very elegant, but if it works...

Let's verify with a slow server and impatient client :

# Impatient Client
use IO::Socket::INET;

$sock = new IO::Socket::INET(
    PeerAddr => "localhost",
    PeerPort => "10007",
    Proto => 'tcp',
    Timeout => 2,
    );  

print <$sock>;

close($sock);


# SlowServer
use IO::Socket::INET;

$sock = new IO::Socket::INET(
    LocalAddr => "localhost",
    LocalPort => "10007",
    Proto => 'tcp',
    Listen => 1,
    Reuse => 1,
    );

$newsock = $sock->accept();
sleep 5;

#while (<$newsock>) {
#    print $_;
#}
print $newsock "Some Stuff";
close($newsock);
close($sock);

if we run this:

pti@pti-laptop:~/playpen$ perl server.pl&
[1] 9130
pti@pti-laptop:~/playpen$ time perl test.pl
Some Stuff[1]+  Done                    perl server.pl

real    0m5.039s
user    0m0.050s
sys     0m0.030s

So it ignores the 2 second timeout and runs for the full 5 seconds.

Now the other impatient client :

use IO::Socket::INET;
eval {
  local $SIG{ALRM} = sub { die 'Timed Out'; };
  alarm 2;
  $sock = new IO::Socket::INET(
    PeerAddr => "localhost",
    PeerPort => "10007",
    Proto => 'tcp',
    Timeout => 2,
    );

  print <$sock>;

  close($sock);
  alarm 0;
};
alarm 0; # race condition protection 
print "Error: timeout." if ( $@ && $@ =~ /Timed Out/ );
print "Error: Eval corrupted: $@" if $@;

~

and running it :

pti@pti-laptop:~/playpen$ perl server.pl&
[1] 9175
pti@pti-laptop:~/playpen$ time perl test2.pl
Error: timeout.Error: Eval corrupted: Timed Out at test2.pl line 3.

real    0m2.040s
user    0m0.020s
sys         0m0.010s

Yep, this timeouts after 2 seconds as expected.

Downturn answered 25/8, 2010 at 22:5 Comment(6)
The Timeout attribute is not ignored, but it is used in the constructor of IO::Socket, not IO::Socket::INET. This is a good workaround though, and something like this is necessary for Windows.Dorsy
That's true, but it's use is commented out in the configure sub of IO::Socket::INET. AFAICS it is set, but never used in ::INET.Downturn
Thanks, that worked for me. But, Why that part is commented in the source ?Polyandry
Note that the second alarm 0 calls in the examples given aren't "race condition protection" per se. Instead, this technique (that is, eval { alarm $n; ...; alarm 0; }; alarm 0) is designed to recover from code that die()s of something other than a user-installed ALRM handler, which would exit the eval{} block without canceling the pending alarm. (Of course, the recovery in this case is itself racy, but that's a different story.)Euphorbia
The code in the question works for me. Maybe they fixed it.Nummulite
For whatever reason, the code that would actually do anything with the Timeout parameter has been commented out and untouched since 1998 when it was first committed to the perl5 tree: github.com/Perl/perl5/blame/blead/dist/IO/lib/IO/Socket/INET.pm github.com/perl/perl5/commit/…Rabia
H
2

So much easier is to use the IO::Socket::Timeout

as per below and it works like a charm.

use IO::Socket::Timeout;
my $socket = IO::Socket::INET->new( Timeout => 2 );
IO::Socket::Timeout->enable_timeouts_on($socket);
$socket->read_timeout(0.5);    # These will work
$socket->write_timeout(0.5);   # These will work
Hyetograph answered 23/12, 2020 at 8:22 Comment(1)
It doesn't fully work on Windows though, and depending on the implementation might misbehave when PerlIO intercepted methods are invoked to read/write into the socket. Also note that the question was about "connection" timeout and IO::Socket::Timeout mostly deals with read/write timeouts as per your 2 last lines in the example code.Blatt

© 2022 - 2024 — McMap. All rights reserved.