jerith ([info]jerith) wrote,
@ 2008-01-24 17:44:00
Previous Entry  Add to memories!  Tell a Friend  Next Entry
Entry tags:programming, rant, ruby

Ruby rant: Timeout::Error
I am currently filled with hate and loathing. I'm going to need a few moments to cool off before I carry on working.

I was trying to make an http call from an irb session full of state I had carefully collected. Most of the code I used is in a script, but I had been doing a substantial amount of exploration over data from a few different place. While waiting for a large dataset from a busy server on the other end of a high-latency link, I got a timeout in the HTTP receive stuff. This in itself would not be a problem, except it crashed my irb session.

The problem is with the exception that a timeout raises:

module Timeout
  class Error < Interrupt
  end
end

The initiated will know that (a subset of) the Ruby exception hierarchy looks something like this:
Exception
  - StandardError
    - RuntimeError
    - ZeroDivisionError
  - ScriptError
    - SyntaxError
  - SystemExit
  - SignalException
    - Interrupt

The important bit there is that all the stuff you can reasonably expect to recover from is under StandardError. Because of this, a default rescue block will not catch anything that isn't a StandardError. The observant reader will notice that I helpfully showed Interrupt's position in the hierarchy. The observant reader will also notice that it is not a subclass of StandardError. This means that you need to catch it explicitly, or it will cause a crash.

Now, my theory is that Timeout was written by someone who misinterpreted "Interrupt" as meaning "interrupt what I'm doing", not "interrupt my application". This would be understandable, although still not acceptable, in a third-party library. Timeout is a core standard library module, however.

To summarise: If you're using the standard Ruby timeout mechanism, which you are if you use the HTTP libraries and almost certainly a whole host of others, you need to explicitly catch Timeout::Error or have a dodgy network bring your entire application crashing down around you.



(12 comments) - (Post a new comment)


[info]chalain
2008-01-24 09:32 pm UTC (link)
Stupid question:

does a tailed rescue still catch it?

E.g. is this code susceptible to crashing:
require "open-uri" # Note this depends on Net/Http

def remote_checksum(file_url)
  open("#{file_url}.md5").readline rescue ""
end


My intent was to create code that would get the md5 checksum contents (first and only line of the file) and return "" if anything else happens.

Do blind-tail rescues still only catch StandardError? I guess I should test for this....

(Reply to this) (Thread)


[info]jerith
2008-01-24 10:35 pm UTC (link)
irb(main):002:0> begin
irb(main):003:1* raise Timeout::Error.new()
irb(main):004:1> rescue
irb(main):005:1> puts "foo"
irb(main):006:1> end
(irb):3:in `irb_binding': Timeout::Error (Timeout::Error)
        from /usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'
        from /usr/lib/ruby/1.8/irb/workspace.rb:52
[jerith@jerith-lap1 ~]$ 
And
irb(main):002:0> raise Timeout::Error.new rescue ""
(irb):2:in `irb_binding': Timeout::Error (Timeout::Error)
        from /usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'
        from /usr/lib/ruby/1.8/irb/workspace.rb:52
So yes, it is broken in that case. You need to explicitly rescue Exception (which is a horrifically bad idea) or Timeout::Error, in addition to all your other exceptions.

The other problem is that this isn't made apparent anywhere until you get dumped out of an irb session or your supposedly bulletproof app falls over. I wonder how much production code out there suffers from this? I know the stock XML-RPC library does, because that's where I encountered it in the first place, at the cost of about half an hour's work. (I ended up not redoing it anyway, because power went away and the network was flakey, but that's a different rant entirely.)

(Reply to this) (Parent)(Thread)

setting the timeout
(Anonymous)
2008-03-09 07:31 am UTC (link)
Is there a way to set the timeout so be longer or shorter?

(Reply to this) (Parent)(Thread)

Re: setting the timeout
[info]jerith
2008-03-09 08:10 am UTC (link)
Timeout::timeout() takes a parameter that specifies the number of seconds to wait. If you're talking about something that uses it internally, you need to either hope they expose the timeout value somewhere (in a constructor, perhaps) or monkey-patch it.

(Reply to this) (Parent)


[info]southernmyst
2008-01-27 03:41 am UTC (link)
*hugs*

(Reply to this)


(Anonymous)
2008-08-25 06:33 pm UTC (link)
Timeout.timeout accepts the exception class as the second parameter. I simply created a BenignTimeout class whose parent is StandardError.
Timeout.timeout(10, BenignTimeout) { do_something } rescue false

(Reply to this) (Thread)


[info]jerith
2008-08-26 07:19 am UTC (link)
Sure, if I'm the one using Timeout. (I prefer not to, because it is known to be broken, but sometimes the breakage isn't critical and doing things properly is infeasible.)

The problem is that Timeout is often used by code I call, but didn't write. Thus, I must either catch the appropriate exception or find all occurrences and monkey-patch them. I do this in some of my code with protocol.rb so that I don't have to handle Timeout::Errors every time I make a network call. It's still pain I shouldn't have to suffer, though.

(Reply to this) (Parent)(Thread)

If you're ok with hacking the core libraries
(Anonymous)
2008-12-18 09:56 am UTC (link)
alias :old_timeout :timeout

class NonfatalTimeout < StandardError
end

def timeout(time, ex=NonfatalTimeout, &block)
old_timeout(time, ex, &block)
end

(Reply to this) (Parent)(Thread)

Re: If you're ok with hacking the core libraries
(Anonymous)
2008-12-18 10:03 am UTC (link)
Of course that could have unintended consequences if other libs rely on catching Timeout::Error.

Fucking hell.

(Reply to this) (Parent)(Thread)

Re: If you're ok with hacking the core libraries
[info]jerith
2008-12-18 10:05 am UTC (link)
Quite.

(Reply to this) (Parent)(Thread)

Re: If you're ok with hacking the core libraries
(Anonymous)
2009-05-12 08:43 pm UTC (link)
What's wrong with putting this at the top of your code?

module Timeout
class Error < StandardError
end
end

(Reply to this) (Parent)(Thread)

Re: If you're ok with hacking the core libraries
[info]jerith
2009-05-13 10:04 am UTC (link)
I'm pretty sure I tried this, but it seems to work now.

Of course, it still suffers from the issue of breaking code that expects the usual hierarchy.

(Reply to this) (Parent)


(12 comments) - (Post a new comment)

Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…