Published 2023-08-11.
Time to read: 1 minutes.
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:
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 ''
.
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