Forum: Ruby timeout when listening with TCPServer

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
F330a44fdf083a7a30488efc47759f3f?d=identicon&s=25 Shea Martin (Guest)
on 2006-03-28 19:51
(Received via mailing list)
I want to listen for connections for 2 seconds, then timeout.  Do I have
  to use the Timeout module?

~S
4feed660d3728526797edeb4f0467384?d=identicon&s=25 Bill Kelly (Guest)
on 2006-03-28 20:47
(Received via mailing list)
From: "Shea Martin" <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
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-03-28 21:20
(Received via mailing list)
On Mar 28, 2006, at 9:48 AM, Shea Martin 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 Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-03-28 21:23
(Received via mailing list)
On Mar 28, 2006, at 10:47 AM, Bill Kelly 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 Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com
4feed660d3728526797edeb4f0467384?d=identicon&s=25 Bill Kelly (Guest)
on 2006-03-28 22:23
(Received via mailing list)
From: "Eric Hodel" <drbrain@segment7.net>
> 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
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2006-03-28 22:33
(Received via mailing list)
Bill Kelly 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
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2006-03-28 22:43
(Received via mailing list)
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.
4feed660d3728526797edeb4f0467384?d=identicon&s=25 Bill Kelly (Guest)
on 2006-03-28 22:48
(Received via mailing list)
From: "Joel VanderWerf" <vjoel@path.berkeley.edu>
>>> 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. :)


Regards,

Bill
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-03-28 23:03
(Received via mailing list)
On Mar 28, 2006, at 12:23 PM, Bill Kelly wrote:

> From: "Eric Hodel" <drbrain@segment7.net>
>>
>> On Mar 28, 2006, at 9:48 AM, Shea Martin 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 :(

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

class MyTimeout < Timeout::Error; end

Timeout.timeout 2, MyTimeout do end

--
Eric Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com
4feed660d3728526797edeb4f0467384?d=identicon&s=25 Bill Kelly (Guest)
on 2006-03-28 23:11
(Received via mailing list)
From: "Joel VanderWerf" <vjoel@path.berkeley.edu>
>
> 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
4feed660d3728526797edeb4f0467384?d=identicon&s=25 Bill Kelly (Guest)
on 2006-03-28 23:27
(Received via mailing list)
From: "Eric Hodel" <drbrain@segment7.net>
>
>> 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 :)

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
4feed660d3728526797edeb4f0467384?d=identicon&s=25 Bill Kelly (Guest)
on 2006-03-28 23:35
(Received via mailing list)
> Thread#unlock in the ensure block of Thread#synchronize

s/Thread/Mutex/g   # :-o
F330a44fdf083a7a30488efc47759f3f?d=identicon&s=25 Shea Martin (Guest)
on 2006-03-29 17:09
(Received via mailing list)
Shea Martin 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:

<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
4feed660d3728526797edeb4f0467384?d=identicon&s=25 Bill Kelly (Guest)
on 2006-03-29 19:23
(Received via mailing list)
From: "Shea Martin" <null@void.0>
>     l_socket = l_listener.accept
> rescue Timeout::Error
> 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
This topic is locked and can not be replied to.