Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is Ruby TCPSocket timeout defined?

Tags:

ruby

sockets

$ irb
1.9.3-p448 :001 > require 'socket'
 => true 
1.9.3-p448 :002 > TCPSocket.new('www.example.com', 111)

gives

Errno::ETIMEDOUT: Operation timed out - connect(2)

Questions:

  • How can I define the timeout value for TCPSocket.new?
  • How can I properly catch the timeout (or, in general, socket) exception(s)?
like image 962
ohho Avatar asked Jan 09 '14 07:01

ohho


3 Answers

At least since 2.0 one can simply use Socket::tcp:

Socket.tcp("www.ruby-lang.org", 10567, connect_timeout: 5) {}

Note the block at the end of the expression, which is used to get connection closed in case such is established.

For older versions @falstru answer appears to be best.

like image 157
akostadinov Avatar answered Nov 01 '22 23:11

akostadinov


Use begin .. rescue Errno::ETIMEDOUT to catch the timeout:

require 'socket'

begin
  TCPSocket.new('www.example.com', 111)
rescue Errno::ETIMEDOUT
  p 'timeout'
end

To catch any socket exceptions, use SystemCallError instead.

According to the SystemCallError documentation:

SystemCallError is the base class for all low-level platform-dependent errors.

The errors available on the current platform are subclasses of SystemCallError and are defined in the Errno module.


TCPSocket.new does not support timeout directly.

Use Socket::connect_non_blocking and IO::select to set timeout.

require 'socket'

def connect(host, port, timeout = 5)

  # Convert the passed host into structures the non-blocking calls
  # can deal with
  addr = Socket.getaddrinfo(host, nil)
  sockaddr = Socket.pack_sockaddr_in(port, addr[0][4])

  Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
    socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

    begin
      # Initiate the socket connection in the background. If it doesn't fail 
      # immediatelyit will raise an IO::WaitWritable (Errno::EINPROGRESS) 
      # indicating the connection is in progress.
      socket.connect_nonblock(sockaddr)

    rescue IO::WaitWritable
      # IO.select will block until the socket is writable or the timeout
      # is exceeded - whichever comes first.
      if IO.select(nil, [socket], nil, timeout)
        begin
          # Verify there is now a good connection
          socket.connect_nonblock(sockaddr)
        rescue Errno::EISCONN
          # Good news everybody, the socket is connected!
        rescue
          # An unexpected exception was raised - the connection is no good.
          socket.close
          raise
        end
      else
        # IO.select returns nil when the socket is not ready before timeout 
        # seconds have elapsed
        socket.close
        raise "Connection timeout"
      end
    end
  end
end

connect('www.example.com', 111, 2)

The above code comes from "Setting a Socket Connection Timeout in Ruby".

like image 30
falsetru Avatar answered Nov 02 '22 00:11

falsetru


If you like the idea of avoiding the pitfalls of Timeout, but prefer to avoid having to deal with your own implementation of the *_nonblock+select implementation, you can use the tcp_timeout gem.

The tcp_timeout gem monkey-patches TCPSocket#connect, #read, and #write so that they use non-blocking I/O and have timeouts that you can enable.

like image 26
womble Avatar answered Nov 01 '22 23:11

womble