12
$ 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)?
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
ohho
  • 50,879
  • 75
  • 256
  • 383
  • Possible duplicate of [Ruby - See if a port is open](http://stackoverflow.com/questions/517219/ruby-see-if-a-port-is-open). And simplest answer for at least ruby 2+: http://stackoverflow.com/a/38266150/520567 – akostadinov Jul 08 '16 at 12:03

4 Answers4

18

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.

akostadinov
  • 17,364
  • 6
  • 77
  • 85
  • This is a newer and better socket component than the TCPSocket. Helped my case. Thanks! – DaCart Mar 23 '17 at 22:41
  • For those for whom this isn't obvious: you don't actually want the `{}` at the end; that's the block where you would put your code, but if you just want the socket itself just remove the block. See https://ruby-doc.org/stdlib-2.0.0/libdoc/socket/rdoc/Socket.html#method-c-tcp – Xiong Chiamiov May 14 '18 at 19:19
  • This doesn't really fix the issue of read timeout? Or am I reading it wrong? – bbozo Mar 25 '20 at 18:46
  • 1
    @bbozo , I looked again at this. I believe read timeout depends on the method call you are using to read from the socket. Whether it is blocking, whether you use `s.read`, `s.read_nonblock`, `s.select` ... I don't think there is such a generic feature `read_timeout`. See how `NET::HTTP` does it in `Net::BufferedIO#rbuf_fill`. – akostadinov Mar 26 '20 at 19:02
12

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".

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
falsetru
  • 357,413
  • 63
  • 732
  • 636
3

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.

womble
  • 12,033
  • 5
  • 52
  • 66
2

You to make a timeout you can use ruby's Timeout module:

reqiure 'socket'
reqiure 'timeout'

begin 
   Timeout.timeout(10) do
      begin
         TCPSocket.new('www.example.com', 111)
      rescue Errno::ENETUNREACH
         retry # or do something on network timeout
      end
   end
rescue Timeout::Error
   puts "timed out"
   # do something on timeout
end

and you'll get after 10 seconds:

# timed out
# => nil

NOTE: Some people may think that it is dangerous solution, well, this opinion has right to exist, but there were no real investigations proceeded, so, that opinion is just a hypothesis. And currently it is better to use internal ruby's timeout engine in Socket class like the following:

Socket.tcp("www.ruby-lang.org", 80, connect_timeout: 80) do |sock|
...
end
Малъ Скрылевъ
  • 16,187
  • 5
  • 56
  • 69
  • This is a very simple solution and yet effective. It should be voted up against all the socket complexity ... !! +1 – Nikkolasg Apr 04 '15 at 15:52
  • 14
    This is a very dangerous solution and should be avoided, as mentioned [in this article](http://spin.atomicobject.com/2013/09/30/socket-connection-timeout-ruby/). – Myst Jul 05 '15 at 21:54