Exception

A nebulous monster that can imitate any other creature in the hierarchy.

Ruby's Exception class

Exception is the top level class in Ruby that all errors inherit from. StandardError and its descendants are application errors, while all other exceptions are used internally by Ruby.

Chances are you won’t see Exception appear in Honeybadger, and if you do, you should investigate to see why it was reported rather than one of its subclasses, which are more common.

Superclass

Object

Methods

  • exception
  • ==
  • to_s
  • message
  • inspect
  • backtrace
  • backtrace_locations
  • set_backtrace
  • cause

Enemies

Honeybadger

Children

» What is it?

The Exception class in Ruby is what all other errors inherit from; it's the top level exception, and it should very rarely — if ever — be rescued. In fact, some obscure bugs can actually be caused by rescuing Exception in the wrong place.

» Why you should never rescue Exception

Exception has a number of subclasses; some are recoverable while others are not. All recoverable errors inherit from the StandardError class, which itself inherits directly from Exception.

The other direct-descendants of Exception and their children are used by Ruby internally for other purposes. In many cases they are unrecoverable exceptions; the Ruby program cannot recover gracefully and must crash when they are encountered.

When you rescue Exception, you also rescue all of its children — including all of the unrecoverable exceptions:

begin
  do_something_risky()
rescue Exception => e
  # Don't do this. This will swallow every single exception. Nothing gets past it.
end

» Ruby's Internal Exceptions

There are only a handful of internal Exceptions that Ruby uses, and like we already mentioned, rescuing all of them indiscriminately is usually not a good idea (although rescuing some of them individually is fairly common). These are Ruby's internal exceptions.

» NoMemoryError

NoMemoryError is raised by Ruby when memory allocation fails. This does not mean that the system is out of memory; rather, it indicates that Ruby attempted to initialize an object that would consume more memory than the system allows.

On 64 bit systems, for example, the maximum allowed value of a String is 2**63 - 1. We can see that Ruby will not allow us to allocate a string longer than that:

s = String.new("1" * (2**63))

raises the exception:

RangeError: bignum too big to convert into `long'

However, while Ruby is capable of handling a String slightly shorter than the maximum, available memory is often a limiting factor:

s = String.new("1" * (2**62))

raises the exception:

NoMemoryError: failed to allocate memory

» LoadError

This exception is raised when a file required fails to load:

require 'these/are/not/the/scripts/youre/looking/for'

raises the exception:

LoadError: no such file to load -- these/are/not/the/scripts/youre/looking/for

A common pattern in Ruby is to rescue LoadError specifically (which is OK, since it's not rescuing any other exceptions) to load an optional dependency which may not be present:

being
  require 'rack'
rescue LoadError
  puts "Rack is not present, but it's OK because we expected it"
end

Because of this, if your Ruby application is crashing due to a missing constant belonging to a file which has been required, it's possible that the LoadError is being rescued improperly.

» NotImplementedError

NotImplementedError is raised when a feature is not implemented on the current platform. For example, if the underlying operating system or Ruby runtime doesn't support forking processes (via the fork system call), Ruby will raise NotImplementedError.

» SyntaxError

SyntaxError is raised when Ruby encounters code with an invalid syntax:

eval("puts('Forgot something)")

raises the following exception:

SyntaxError: (eval):1: unterminated string meets end of file

» SecurityError

SecurityError is part of Ruby's security model; it's raised when a potentially unsafe operation is attempted.

For example, every Ruby object that comes from an external source (such as a String read from a file, or an environment variable) is automatically marked as being "tainted". When your Ruby program works with tainted objects, it may raise the SecurityError exception when an operation is not permitted, such as attempting to eval a tainted String:

$SAFE = 1
foo = "puts 'hello world'"
foo.taint
eval(foo)

raises the exception:

SecurityError: Insecure operation - eval (SecurityError)

» Interrupt

Interrupt is raised when an interrupt signal is received from the operating system; typically when a user presses Control-C:

puts "Press CTRL-C to quit the loop"
loop {}

raises the exception:

Interrupt

» SystemExit

SystemExit is raised by exit to initiate the termination of the Ruby program:

begin
  exit(1)
rescue SystemExit => e
  puts "the ruby program is exiting with status #{e.status}"
  raise
end

prints the output:

the ruby program is exiting with status 1

and exits with status 1.

If you rescue SystemExit without re-raising the exception, your Ruby program will continue to execute until it exits at the next opportunity, and the status code may be be different from the one that was rescued:

begin
  exit(1)
rescue SystemExit => e
  puts "the ruby program is exiting with status #{e.status}"
end

exit(0)

prints the output:

the ruby program is exiting with status 1

but exits with status 0.

» SystemStackError

SystemStackError is one of the more popular exceptions across all programming languages. If you've ever gotten a cryptic "stack level too deep" error in Ruby, you've encountered a SystemStackError exception:

def malcovitch_malcovitch
  malcovitch_malcovitch
end
malcovitch_malcovitch

raises the exception:

SystemStackError: stack level too deep

» fatal

fatal is an Exception that Ruby raises when it encounters a fatal error and must exit. It is impossible to rescue fatal, and it's impossible to raise it artificially.

» If you must...

First of all, we'll say it again: don't rescue the Exception class in Ruby. If you must rescue Exception, such as for error reporting, always re-raise the exception promptly:

begin
  do_something_risky()
rescue Exception => e
  logger.fatal("Encountered an Exception: #{e}")
  raise e # <-- always do this!
end

Fun fact: Rails 5.1 rescues Exception 37 times.