timeout when listening with TCPServer

S

Shea Martin

I want to listen for connections for 2 seconds, then timeout. Do I have
to use the Timeout module?

~S
 
B

Bill Kelly

From: "Shea Martin said:
I want to listen for connections for 2 seconds, then timeout. Do I have
to use the Timeout module?

You could use select:

timeout_sec = 2.0
ios = select([@server], nil, nil, timeout_sec)
if ios
client = @server.accept
end

Note that you'll probably want to put the socket into
nonblocking mode. It's possible for select to return
"ready to read" and have accept still block, if the
client happens to disconnect in the small window
between the select and the accept.


Regards,

Bill
 
E

Eric Hodel

I want to listen for connections for 2 seconds, then timeout. Do I
have to use the Timeout module?

That will be the easiest way.

Timeout.timeout 2 do
Thread.start server.accept do |s| new_connection s end
end
 
E

Eric Hodel

From: "Shea Martin said:
I want to listen for connections for 2 seconds, then timeout. Do
I have to use the Timeout module?

You could use select:

timeout_sec = 2.0
ios = select([@server], nil, nil, timeout_sec)
if ios
client = @server.accept
end

Unfortunately this won't accept connections for two seconds. If a
client connects at .5 seconds then immediately disconnects you'll
need extra code to loop until you've consumed the full 2 seconds.
Note that you'll probably want to put the socket into nonblocking
mode. It's possible for select to return
"ready to read" and have accept still block, if the
client happens to disconnect in the small window between the select
and the accept.

Yuck, timeout will give a cleaner solution with less work.
 
B

Bill Kelly

From: "Eric Hodel said:
That will be the easiest way.

Timeout.timeout 2 do
Thread.start server.accept do |s| new_connection s end
end

Oh, hey, cool. Using the thread there looks like it
ought to avoid the issue with Timeout firing in the
middle of ensure blocks and circumventing them?

I've been avoiding Timeout like "the plague" since
running into that behavior.


Regards,

Bill
 
J

Joel VanderWerf

Bill said:
Oh, hey, cool. Using the thread there looks like it
ought to avoid the issue with Timeout firing in the
middle of ensure blocks and circumventing them?

I've been avoiding Timeout like "the plague" since
running into that behavior.

But the timeout won't stop the thread.

$ cat timeout.rb
require 'timeout'

th = nil
begin
Timeout.timeout 2 do
th = Thread.new do
loop {puts "in thread"; sleep 1}
end
end
rescue TimeoutError
puts "timeout!"
end

sleep 5
p th.alive?

$ ruby timeout.rb
in thread
in thread
in thread
in thread
in thread
true
 
J

Joel VanderWerf

Joel said:
But the timeout won't stop the thread.

Ignore my last post. Not stopping the thread is probably exactly the
behavior that you want. The timeout affects only the server.accept call
(and the Thread.start). So this code is allowing 2 sec for a connection,
but no placing a limit on what happens after that.

I think it might be clearer to write it as

session = Timeout.timeout 2 do
server.accept
end

Thread.start(session) do |s| new_connection s end

but that is only my unconsidered opinion.
 
B

Bill Kelly

From: "Joel VanderWerf said:
But the timeout won't stop the thread.

I don't think it was supposed to. The #accept is
happening as an argument to Thread.start, so the thread
won't be started until the connection is accepted.

The only teeny race condition I can see is if the
timeout fires after server.accept but before
Thread.start is invoked. That would seem to leak
the socket - but the GC would normally reclaim it
eventually (except in cases where it wouldn't. :)


Regards,

Bill
 
E

Eric Hodel

Ooh, I forgot the loop around Thread.start :(
Oh, hey, cool. Using the thread there looks like it
ought to avoid the issue with Timeout firing in the
middle of ensure blocks and circumventing them?

I've been avoiding Timeout like "the plague" since
running into that behavior.

You've timed out, so that's what's supposed to happen :)

Its easy to know you've timed out though:

require 'timeout'

Timeout.timeout 2 do
begin
ensure
begin
sleep
rescue Timeout::Error
puts 'caught!'
end
end
end

But, don't nest Timeout blocks without changing the raised
exception. Your code will eventually catch the wrong error and do
something bad.

$ ri Timeout.timeout
-------------------------------------------------------- Timeout#timeout
timeout(sec, exception=Error) {|if sec == nil or sec.zero?| ...}
 
B

Bill Kelly

From: "Joel VanderWerf said:
I think it might be clearer to write it as

session = Timeout.timeout 2 do
server.accept
end

Thread.start(session) do |s| new_connection s end

I think I'd do it that way too. (Probably adding a
rescue for Timeout::Error.)

I wonder if, there's still any possible race condition
in this version.

session = Timeout.timeout 2 do
server.accept
# If timeout fires "here" would the socket be leaked?
# (eventual probable reaping by GC notwithstanding)
end

Not that I would lose much sleep over it. But I try
to write code without any possibility of race conditions
whenever possible.

I'm guessing the race condition does exist there. Anyone
know whether assignments are atomic with regard to
exceptions? E.g.

begin
Timeout.timeout 2 do
session = server.accept
# ^^^ atomic?
end
rescue Timeout::Error
end

Any possibility of the timeout interrupting the assignment
to "session" there? If assignment is atomic then we
should be able to avoid leaking the socket.

(Just wondering... not too worried about it in practical
terms with this particular example.)


Regards,

Bill
 
B

Bill Kelly

From: "Eric Hodel said:
You've timed out, so that's what's supposed to happen :)

Haha, yes... But it can be tricky...

Timeout.timeout 2 do
sleep 1.9
mutex.synchronize { @shared << "foo" }
end

...the timeout can fire in such a way that the
Thread#unlock in the ensure block of Thread#synchronize
is skipped - leaving the mutex locked.

I've been bitten by this... and it was pretty tough to
track down. (Of course, I was calling some harmless
looking method in the timeout block that just happened
to be using a mutex as part of its implementation...)


Regards,

Bill
 
S

Shea Martin

Shea said:
I want to listen for connections for 2 seconds, then timeout. Do I have
to use the Timeout module?

~S


My situation is more like this:

<code>
Thread.new do

begin
l_exit_status = false

l_listener = TCPServer.new( HOST, p_port )
l_socket = false

Timeout.timeout( 2 ) do
l_socket = l_listener.accept
end #timeout

#use socket
l_socket.put "send you requested data"

l_exit_status = true

rescue SocketError
$log.puts "socket error"
l_exit_status = false
rescue Timeout::Error
$log.puts "you only get 2 seconds to come and get the data"
l_exit_status = false
rescue Errno::EBADF
$log.puts "could not open connection"
l_exit_status = false
ensure
l_socket.close if l_socket
end #begin/rescue

l_exit_status
end #thread/do
</code>

I don't think I have a race condition here, do I?

~S
 
B

Bill Kelly

From: "Shea Martin said:
<code>
Thread.new do

begin
l_exit_status = false

l_listener = TCPServer.new( HOST, p_port )
l_socket = false

Timeout.timeout( 2 ) do
l_socket = l_listener.accept
end #timeout

#use socket
l_socket.put "send you requested data"

l_exit_status = true

rescue SocketError
$log.puts "socket error"
l_exit_status = false
rescue Timeout::Error
$log.puts "you only get 2 seconds to come and get the data"
l_exit_status = false
rescue Errno::EBADF
$log.puts "could not open connection"
l_exit_status = false
ensure
l_socket.close if l_socket
end #begin/rescue

l_exit_status
end #thread/do
</code>

I don't think I have a race condition here, do I?

I'm not 100% sure myself, because I don't know whether
this assignment:

l_socket = l_listener.accept
^^^

is atomic, or not.

However, I'm not suggesting to worry about it, either.

Practically speaking it seems unlikely to be a problem,
and ruby's garbage collector will generally recover
orphaned file handles.


Regards,

Bill
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,186
Latest member
vinaykumar_nevatia

Latest Threads

Top