Ruby Exception Handling

Ruby Exception Handling: SystemExit

As we approach the end of our all-encompassing Ruby Exception Handling series, today we’ll be taking a look at the SystemExit error, which is raised whenever the Kernel#exit method is invoked to terminate the current process.

In this article we’ll explore the SystemExit exception in more detail by looking at where it resides in the Ruby Exception class hierarchy, as well as showing a few code examples to illustrate how SystemExist exceptions are typically raised, so let’s get started!

The Technical Rundown

  • All Ruby exceptions are descendants of the Exception class, or a subclass therein.
  • SystemExit is the direct descendant of the Exception class.

When Should You Use It?

Since SystemExit exceptions are only raised by calling the Kernel#exit method it might benefit us to take a closer look at how this method works and how Ruby handles closing the current process in general. We’ll start with the full code example below (well, one of them anyway) and then we’ll explore it in more detail afterward:

Calling the Kernel#exit method in Ruby initiates a termination of the current Ruby script. It also raises a SystemExit exception at the same time, which we can catch and use for our own purposes during the final process termination events.

To that end we start with a simple #exit_process method that calls Kernel#exit, tries to log a message within the same block as exit and immediately afterward, then also rescues the SystemExit exception and outputs a few more messages for us:

As you can see the output of "Exit stopped." never executes because of the invocation of Kernal#exit, which immediately begins the termination process and only allows for the raising of our SystemExit exception, along with any final garbage collection-style cleanup methods.

One example of handling process exiting in Ruby is to use the Kernel#at_exit method. Kernel#at_exit expects a block to be passed that is converted into a Proc and registers it for execution when the current process is exiting. This allows us to easily perform any sort of cleanup procedures we need to before our application shuts down.

For example, the #at_exit_example method starts by specifying a Kernel#at_exit block that just outputs a message for testing purposes, then continues on to call Kernel#exit just as we did before, along with the associated output messages:

To see what’s going on here we’ll show the full output log:

As you can see we begin the exit process then rescue our SystemExit exception, before finalizing the termination procedure by calling the Proc we created with Kernel#at_exit. Cool!

Our last example shows how to use the ObjectSpace::define_finalizer method. Unlike Kernel#at_exit, which behaves as a global Proc that is called when the current process executes no matter what, the ObjectSpace::define_finalizer method accepts an Object as the first argument. When that object is destroyed the Proc that is passed as the second argument is executed. This is particularly handy for invoking special finalization logic when a class instance is destroyed.

For example, here we’re using a tried-and-true Book class model with a few simple fields. Within the #initialize method we also define a finalizer that applies to self (the instance of Book that is being destroyed). We’ve opted to use our own #finalize method to accomplish this and return a Proc to be invoked, but we could just as easily created an inline Proc instead:

Within the #finalizer_example method we create a new Book instance and then exit the process using the Kernel#exit method again. The result is that we still raise a SystemExit exception since we called Kernel#exit, but afterward the Ruby garbage collector destroys our book instance that was created, generating the Destroying... text output we were after:

To get the most out of your own applications and to fully manage any and all Ruby Exceptions, check out the Airbrake Ruby exception handling tool, offering real-time alerts and instantaneous insight into what went wrong with your Ruby code, including integrated support for a variety of popular Ruby gems and frameworks.