Timeout when listening with TCPServer


#1

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

~S


#2

From: “Shea M.” null@void.0

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


#3

On Mar 28, 2006, at 9:48 AM, Shea M. wrote:

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


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#4

On Mar 28, 2006, at 10:47 AM, Bill K. wrote:

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.


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#5

From: “Eric H.” removed_email_address@domain.invalid

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


#6

Bill K. wrote:

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.

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


#7

Joel VanderWerf wrote:

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.

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.


#8

On Mar 28, 2006, at 12:23 PM, Bill K. wrote:

From: “Eric H.” removed_email_address@domain.invalid

On Mar 28, 2006, at 9:48 AM, Shea M. wrote:

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

Ooh, I forgot the loop around Thread.start :frowning:

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 :slight_smile:

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?| …}

class MyTimeout < Timeout::Error; end

Timeout.timeout 2, MyTimeout do end


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#9

From: “Joel VanderWerf” removed_email_address@domain.invalid

Timeout.timeout 2 do
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. :slight_smile:

Regards,

Bill


#10

From: “Joel VanderWerf” removed_email_address@domain.invalid

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


#11

From: “Eric H.” removed_email_address@domain.invalid

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 :slight_smile:

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


#12

Thread#unlock in the ensure block of Thread#synchronize

s/Thread/Mutex/g # :-o


#13

From: “Shea M.” null@void.0

l_socket = l_listener.accept

rescue Timeout::Error
end #thread/do

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


#14

Shea M. wrote:

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:

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

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

~S