The Perils of "rescue Exception"

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 know if this
    # block of code fails for any reason.
  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.

12 thoughts on “The Perils of "rescue Exception"”

  1. 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. 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. 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.

  4. 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 =>."

  5. 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".

  6. 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.

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

  8. Hi Mike,

    Can you explain how this post relates to your Logging and RetryJobs middleware in sidekiq which rescue Exception explicity? I assume it’s ok because they’re re-raising?

Comments are closed.