Mike Slinn
Mike Slinn

Graceful Crash Exit From Ruby

Published 2023-08-11.
Time to read: 1 minutes.

This page is part of the ruby collection.

Ever want to stop your Ruby program without generating a stack trace? It is simple enough to do if you control the execution environment, but if your program is loaded by another program, then the loading program can trap your attempts to exit.

Jekyll traps exceptions thrown by its plugins. If you attempt to call abort or exit, those calls will work, but there is nothing you can do to prevent a long stack trace from being issued. This is not a pleasant experience for users.

Process.kill

You might try calling Process.kill, like this:

Shell
Process.kill('KILL', Process.pid) # Like kill -9 in bash
# or:
Process.kill('SEGV', Process.pid) # Like kill -11 in bash

Unfortunately, not only does this generate a stack trace, but it also dumps lots of other information. This is worse than just calling abort.

Kernel.exec

The only way I found that worked was to terminate the process by loading another process using Kernel:exec.

The following method prints an error message in red using colorator, then replaces the Jekyll process with the bash command, echo ''.

Shell
require 'colorator'

# If a Jekyll plugin needs to crash exit, and stop Jekyll, call this method.
# It does not generate a stack trace.
# This method does not return because the process is abruptly terminated.
#
# @param error StandardError or a subclass of StandardError is required
#
# Do not raise the error before calling this method, just create it via 'new', like this:
# exit_without_stack_trace StandardError.new('This is my error message')
#
# If you want to call this method from a handler method, the default index for the backtrace array must be specified.
# The default backtrace index is 1, which means the calling method.
# To specify the calling method's caller, pass in 2, like this:
# exit_without_stack_trace StandardError.new('This is my error message'), 2
def exit_without_stack_trace(error, caller_index = 1)
  raise error
rescue StandardError => e
  file, line_number, caller = e.backtrace[caller_index].split(':')
  caller = caller.tr('`', "'")
  warn "#{self.class} died with a '#{error.message}' #{caller} on line #{line_number} of #{file}".red
  exec "echo ''"
end


* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.