Mike Perham

On Ruby, software and the Internet

← Deleting Duplicate Rows in MySQL Converting a MySQL database from Latin1 to UTF8 →

The Perils of “rescue Exception”

March 3rd, 2012 · 9 Comments

Ruby’s standard exception handling looks like this.

  begin
    do_something
  rescue => ex
    # do something with the error
  end

This will catch all StandardErrors and its subclasses. A frequent issue I see in Ruby code is this:

  begin
    do_something
  rescue Exception => ex
    # Apparently I REALLY want to make sure this block 
    # won't interfere will the outside code.
  end

This is a BAD, BAD idea, dear reader, and here’s why. Ruby uses Exceptions for other things than your application code errors. One example is the Interrupt class which is a SignalException. Ruby sends this Exception to all threads so that when the process gets an INT/Ctrl-C signal, all the threads will unwind and the process will shutdown. If you rescue Exception, you will potentially catch this exception and ignore it, making your thread and process an unkillable computing zombie. Your only choice will be to pull out your kill -9 shotgun and aim for the head.

Here’s an example of a Ruby script you cannot shutdown gracefully. Run it and you’ll see exactly the behavior I’ve described.

while true
  begin
    sleep 5
    puts 'ping'
  rescue Exception => ex
    puts "Mmmmm, brains"
  end
end

So remember, your application errors should be subclasses of StandardError and if you want to catch everything, just stick will plain old “rescue => ex”. Your application will behave better for it.

Tags: Ruby

9 responses so far ↓

  • 1 Avdi Grimm // Mar 3, 2012 at 12:30 pm

    Indeed! Another thing I see is “rescue Object”, which is especially silly since Ruby will not allow you to raise an object not derived from Exception.

    For anyone interested in using Ruby exceptions more effectively, may I humbly suggest “Exceptional Ruby”?

  • 2 Bradly Feeley // Mar 3, 2012 at 2:48 pm

    Mike,

    Does the ‘=>’ have a special meaning in ‘rescue => ex’. Obviously you usually see the hash rocket in hashes, so does it have another function that isn’t used very often, or is this just a special use just when rescuing.

  • 3 Mike Perham // Mar 3, 2012 at 6:51 pm

    Humbly seconded, Avdi’s book is illuminating. In fact I was just suggesting it to TheClymb dev team yesterday!

  • 4 Eric Hankins // Mar 6, 2012 at 1:28 pm

    Amen, and amen. I discovered this myself after rescuing Exception in all of my daemons and then wondering why I couldn’t kill them. This is great advice.

  • 5 Steven Harms // Mar 10, 2012 at 11:48 am

    Here’s my theory. This mis-idiom issue is an error caused by over-generalization from a specific case.

    Consider:

    
    require 'pp'
    
    begin
      i= 0
      if i == 0
        1/0
      end
      raise "random runtime exception"
      p 'I should never get executed'
    rescue ZeroDivisionError => ex
      p 'I am rescuing only ZeroDivisionErrors!'
      p "saw an #{ex.class}"
      pp ex.backtrace
      i+=1
    end
    
    pp ZeroDivisionError.ancestors
    

    With output:

    "I am rescuing only ZeroDivisionErrors!"
    "saw an ZeroDivisionError"
    ["./j:7:in `/'", "./j:7"]
    [ZeroDivisionError, StandardError, Exception, Object, PP::ObjectMixin, Kernel]
    

    I think the thought process is:

    "Oh, I handle a ZeroDivsionError or a RuntimeError by putting its name before the =>. I will handle a more general exception by putting 'Exception' in front of the =>."

  • 6 Conrad Irwin // Apr 26, 2012 at 12:51 pm

    In some circumstances you do want to catch *all* the exceptions, for example the REPL loop in Pry shouldn’t die if it gets a TimeoutError or a LoadError.

    Our solution, made with ideas from Avdi’s Exceptional Ruby, was to define a module that matches all exceptions except for the very few we don’t want to catch:

    “`
    # As a REPL, we often want to catch any unexpected exceptions that may have
    # been raised; however we don’t want to go overboard and prevent the user
    # from exiting Pry when they want to.
    module RescuableException
    def self.===(exception)
    case exception
    # Catch when the user hits ^C (Interrupt < SignalException), and assume
    # that they just wanted to stop the in-progress command (just like bash etc.)
    when Interrupt
    true
    # Don't catch signals (particularly not SIGTERM) as these are unlikely to be
    # intended for pry itself. We should also make sure that Kernel#exit works.
    when SystemExit, SignalException
    false
    # All other exceptions will be caught.
    else
    true
    end
    end
    end
    “`

    We then use "rescue RescuableException" in place of "rescue Exception".

  • 7 Judson Lester // May 1, 2012 at 10:53 pm

    The only counterargument I’d make is that (sadly) some gems use their own exception classes that don’t descend from StandardError, which makes rescue => ex inadequate.

  • 8 Code - sane_timeout: A Replacement for Ruby’s Standard Library Timeout // Sep 15, 2012 at 9:14 pm

    [...] should typically only rescue StandardError, or specific other errors appropriate to the context. Mike Perhaps covers the issue here. But the reality is that some code, for reasons bad, questionable, or good, will sometimes rescue [...]

  • 9 Andreas Mohr // Oct 1, 2012 at 1:40 pm

    Care to listen to a counter argument?
    (warning: Ruby n00b here)

    If I’m not mistaken it should be noted that this does not apply to all (perhaps not even most?) cases of exception handling.
    While the
    begin
    rescue => ex
    end
    block design would be useful for cases of *finalized* exception handling (i.e., final handling of an error situation in local context), there are other cases where you would want to have local cleanup *and* have the exception bubble up (i.e., re-throw), to signal a murky situation to upper layers as well.
    In such cases IMHO
    begin
    rescue Exception => e

    raise
    end
    is exactly the right thing to do to ensure that *all* exceptions, not only application-level, are intercepted properly and then passed on.

Leave a Comment

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.