views:

2405

answers:

4

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

+7  A: 

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
+3  A: 

The timeout object is a good solution.

This is a example of asynchronous I/O (nonblocking 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
Jonke
+2  A: 

This article details some problems that the Timeout module can experience. This article shows a way of timing out sockets that might be more performant.

Tim Stewart
+1  A: 

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