Ruby/Gnome app hangs when http'ing from inside a thread (linux only)

A

André

Hello,

I'm writing a Ruby/Gnome application that auto-updates itself from the
internet when a new version is available.

The auto-update function runs in a different thread, so the user won't
even notice that the application is checking for a new version.

It all works fine, EXCEPT when there's no network connection. In this
case, the WHOLE application simply hangs for one or two minutes, with
no regard for the fact that this code is running in a thread, or that
the timeout property is set.

And this error only happens on linux. Has anybody ever experienced
anything like it?

A sample code follows:

Thread.new do
http = Net::HTTP.new('myhost.com')
http.open_timeout = 5
http.read_timeout = 5
http.start
resp = http.get('/somefile.html')
puts resp.body
end

André
 
S

Stephen Lewis

The auto-update function runs in a different thread, so the user won't
even notice that the application is checking for a new version.

It all works fine, EXCEPT when there's no network connection. In this
case, the WHOLE application simply hangs for one or two minutes, with
no regard for the fact that this code is running in a thread, or that
the timeout property is set.

I think your problem may be caused by a blocking name lookup, and
there seems to be a fix in the standard library (see below)


Explanation:

From what I understand, the builtin DNS resolver just hands off to a
blocking system call -- which given the Ruby thread model means that
for the duration of that call no other threads will be scheduled (at
least, in 1.8).

The timeouts you specify won't apply to the system call, and I don't
think there's any way to specify ones that will given the standard
implementation, so it'll wait for the system default timeout (that 2m
delay sounds awfully familiar) before returning an error.


Possible solution:

I think the way to fix this is to replace the default resolver with an
alternative version that plays nicely with ruby threads - i.e. by
wrapping a native async DNS library, or a implementing one in pure
ruby.

While I haven't tried this, I think you might be able to magically
solve this simply through the wonders of the standard library:

require 'resolv-replace'

Which seems to provide a pure-ruby resolver which should play nice
with threads. I'm not sure if there are any consequences of doing
this though.

If that doesn't work, there seem to be several DNS related gems, maybe
someone else can provide more detail.

And this error only happens on linux. Has anybody ever experienced
anything like it?

Why it happens only on linux, I'm not really sure - if this is
something like ethernet with an unplugged network cable, I have this
vague idea that most linuxes don't actually take the interface down
when its unplugged, whereas maybe windows does - so on linux it tries
to send packets and wait for timeouts anyway, but maybe windows
disables/takes down the interface so it returns an immediate error?
Pure speculation on my part :)

Hope that helps (and please excuse the long-windedness :),
Stephen Lewis
 
A

André

I think your problem may be caused by a blocking name lookup, and
there seems to be a fix in the standard library (see below)

...

Thank you SO MUCH! That's exactly what I needed to know. I'll try to
change something and check if it works.

Regards!

André
 
A

André

And this error only happens on linux. Has anybody ever experienced
Why it happens only on linux, I'm not really sure

I made a mistake: the same problem happens both in Linux and Windows.
While I haven't tried this, I think you might be able to magically
solve this simply through the wonders of the standard library:

require 'resolv-replace'

It worked great under Linux! Unfortunately, this seems not to work
under Windows... :( It simply doesn't resolve.
I think the way to fix this is to replace the default resolver with an
alternative version that plays nicely with ruby threads - i.e. by
wrapping a native async DNS library, or a implementing one in pure
ruby.

I think this might be the way to go under Windows. The problem is: the
page where I'm downloading from depends on virtual host. So I can't
open with HTTP.new('10.20.30.40') since it'll return different results
that opening with HTTP.new('virtual.host.com').

Is there any way that I can open a HTTP connection with a IP and then
send a virtual host on the HTTP request?

Regards,

André
 
S

Stephen Lewis

It worked great under Linux! Unfortunately, this seems not to work
under Windows... :( It simply doesn't resolve.

I have no idea why it doesn't work under Windows - looking at the
source it seems to have explicitly support it :(

It might be worth experimenting with Socket.gethostbyname and see if
it is returning any sort of useful errors? But I don't really know
what you should be looking for, sorry :/
I think this might be the way to go under Windows. The problem is: the
page where I'm downloading from depends on virtual host. So I can't
open with HTTP.new('10.20.30.40') since it'll return different results
that opening with HTTP.new('virtual.host.com').

Is there any way that I can open a HTTP connection with a IP and then
send a virtual host on the HTTP request?

You'll need to set the 'Host' header on the request. So using
your example before, do something like:

http = Net::HTTP.new('127.0.0.1')
...
http.start
resp = http.get('/somefile.html', {'Host'=>'myhost.com'})
...

But getting the resolver to work properly is probably a better long
term solution :)
 

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

Forum statistics

Threads
473,982
Messages
2,570,190
Members
46,740
Latest member
AdolphBig6

Latest Threads

Top