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

code2be picture code2be · Aug 25, 2010 · Viewed 26.6k times · Source

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

Answer

Peter Tillemans picture Peter Tillemans · Aug 26, 2010

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.