19

How do you set the timeout for blocking operations on a Ruby socket?

readonly
  • 343,444
  • 107
  • 203
  • 205

3 Answers3

16

The solution I found which appears to work is to use Timeout::timeout:

require 'timeout'
    ...
begin 
    timeout(5) do
        message, client_address = some_socket.recvfrom(1024)
    end
rescue Timeout::Error
    puts "Timed out!"
end
readonly
  • 343,444
  • 107
  • 203
  • 205
  • 15
    Using timeout is a mistake. Due to green theards, anything that blocks on IO will block the timeout thread as well thus stopping it from working. – Sardathrion - against SE abuse Dec 07 '10 at 16:33
  • @Sardathrion, thanks for the feedback but a solution would be better than a negative comment :) – Mike Aug 29 '11 at 22:43
  • 1
    Ruby threads are broken I am afraid. Find a better language -- harsh but true. – Sardathrion - against SE abuse Aug 30 '11 at 06:45
  • 2
    The SystemTimer gem can be used to implement safe timeouts on Ruby 1.8. Ruby 1.9 does not have the timeout limitation described by Sardathrion since it uses native threads. – betamatt Mar 26 '12 at 19:22
  • 3
    This uses threads for timeouts which is very bad for performance in multithreaded applications in ruby. See my answer here: http://stackoverflow.com/a/12111120/216314 – Tyler Brock Aug 24 '12 at 14:19
  • @TylerBrock I am using Rails 3 with Apache/Passenger, which from my understanding is not multithreaded. I think Passenger spawns a new process for each request. Is your solution still recommended over this? – JohnMerlino Jul 12 '14 at 18:39
  • Can anyone confirm that the `Timeout::timeout` implementation above is reasonable for ruby 1.9.x and newer? – Blake Aug 26 '14 at 08:32
  • It is only reasonable if performance is not an issue but yes, this is perfectly reasonable Ruby code. The nuance is in the details of the implementation of the language. – Tyler Brock May 30 '16 at 08:16
  • Ruby 2.x is not using green threads anymore, so all the comments of this not working should be invalid now. – Lothar Jan 18 '22 at 02:54
15

The timeout object is a good solution.

This is an example of asynchronous I/O (non-blocking in nature and occurs asynchronously to the flow of the application.)

IO.select(read_array
[, write_array
[, error_array
[, timeout]]] ) => array or nil

Can be used to get the same effect.

require 'socket'

strmSock1 = TCPSocket::new( "www.dn.se", 80 )
strmSock2 = TCPSocket::new( "www.svd.se", 80 )
# Block until one or more events are received
#result = select( [strmSock1, strmSock2, STDIN], nil, nil )
timeout=5

timeout=100
result = select( [strmSock1, strmSock2], nil, nil,timeout )
puts result.inspect
if result

  for inp in result[0]
    if inp == strmSock1 then
      # data avail on strmSock1
      puts "data avail on strmSock1"
    elsif inp == strmSock2 then
      # data avail on strmSock2
      puts "data avail on strmSock2"
    elsif inp == STDIN
      # data avail on STDIN
      puts "data avail on STDIN"
    end
  end
end
Blake
  • 695
  • 9
  • 23
Jonke
  • 6,525
  • 2
  • 25
  • 40
8

I think the non blocking approach is the way to go.
I tried the mentioned above article and could still get it to hang.
this article non blocking networking and the jonke's approach above got me on the right path. My server was blocking on the initial connect so I needed it to be a little lower level.
the socket rdoc can give more details into the connect_nonblock

def self.open(host, port, timeout=10)
 addr = Socket.getaddrinfo(host, nil)
 sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)

 begin
  sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
 rescue Errno::EINPROGRESS
  resp = IO.select([sock],nil, nil, timeout.to_i)
  if resp.nil?
    raise Errno::ECONNREFUSED
  end
  begin
    sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
  rescue Errno::EISCONN
  end
 end
 sock
end

to get a good test. startup a simple socket server and then do a ctrl-z to background it

the IO.select is expecting data to come in on the input stream within 10 seconds. this may not work if that is not the case.

It should be a good replacement for the TCPSocket's open method.

Pete Brumm
  • 1,656
  • 19
  • 13