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
    );
Peter Tillemans
  • 34,983
  • 11
  • 83
  • 114
code2be
  • 1,358
  • 1
  • 10
  • 15

2 Answers2

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.

Peter Tillemans
  • 34,983
  • 11
  • 83
  • 114
  • 1
    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. – mob Aug 25 '10 at 22:41
  • 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. – Peter Tillemans Aug 25 '10 at 22:48
  • Thanks, that worked for me. But, Why that part is commented in the source ? – code2be Aug 25 '10 at 23:23
  • 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.) – pilcrow Jul 18 '12 at 13:47
  • The code in the question works for me. Maybe they fixed it. – Sam Watkins Sep 23 '16 at 00:49
  • 1
    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: https://github.com/Perl/perl5/blame/blead/dist/IO/lib/IO/Socket/INET.pm https://github.com/perl/perl5/commit/cf7fe8a#diff-a365bb9ac2a017254a49ce6d296fcd040633f84ae66f21783706a70006ebc74eR172 – nuclearpidgeon Jun 30 '23 at 05:33
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
Mark Arnold
  • 253
  • 3
  • 9