» Handle with care!

SignalException is not your everyday exception. It has a specific purpose and is raised when the current application's process receives a signal from the OS (in some rare cases, the current application can also raise this exception).

Every SignalException contains a signo (an integer signal number), and each signal number has a specific meaning and a string identifier associated with it. For instance, the SIGTERM string identifier has an associated integer value of 15 , and is often used to notify an application to clean up and stop.

One example for this is: When a computer is shutting down, the OS initially sends out a SIGTERM(15) signal to all the running applications, and well behaving applications respond to this by closing their resources, persisting unsaved data and shutting down in a clean fashion. If the applications don't stop in time, the operating system will forcibly shut them down using the SIGKILL(9) signal. You may have used a kill -9 many times without knowing that it actually sends a SIGKILL signal to the running application which in turn forcibly shuts the application down, without giving it time to clean up any resources. SIGKILL is a SignalException which cannot be caught and handled by your application.

Let's look at a quick example:

# signal.rb begin puts "Started process: #{ Process . pid } " sleep # wait for the interrupt from outside rescue SignalException => e puts "received Exception #{ e } " end

ruby signal.rb # => Started process: 23498 kill -s TERM 23498 # => received Exception SIGTERM

» How to trap it

You can trap a signal exception in the following ways:

The Signal.trap method allows you to define the signal that you want to trap and run a block when such a signal is received:

# trap_signal.rb def run puts 'Running my app...' sleep # sleep indefinitely to simulate work end def cleanup_and_exit puts 'Oops! Need to shut things down...' puts 'Closing up database connections...' puts 'Persisting unsaved data...' exit end Signal . trap ( 'TERM' ) { cleanup_and_exit } Signal . trap ( 'INT' ) { cleanup_and_exit } run # trigger our run function

ruby trap_signal.rb # => Running my app... # => Hit Ctrl+C on the keyboard # => Oops! Need to shut things down... # => Closing up database connections... # => Persisting unsaved data...

In the above example when you run the app and hit Ctrl+C on your keyboard, it will be caught by the Signal.trap('INT') line and execute the cleanup_and_exit function which will then clean up all the resources and exit (in this case it doesn't really do anything; your real app would have code which closes logs or database connections, etc.).

# rescue_signal.rb def cleanup_and_exit ( exception ) puts "Received a #{ exception } " puts 'Oops! need to shut things down...' puts 'Closing up database connections...' puts 'Persisting unsaved data...' exit end def run puts "Running my app. PID: #{ Process . pid } " sleep # sleep indefinitely to simulate work rescue SignalException => ex cleanup_and_exit ( ex ) end run # trigger our run function

# terminal-1 $ ruby ~/s/rescue_signal.rb Running my app. PID: 2552 # from a different terminal: terminal-2 $ kill -s TERM 2552 # terminal-1 Received a SIGTERM Oops! need to shut things down... Closing up database connections... Persisting unsaved data...

This script behaves in the same way as the trap_signal.rb script.

So, If you are building an app which needs to tidy up things before shutting down, SignalException s are a great way to handle that.

» Signal handling guidelines

Signal Handling should be done thoughtfully; some poorly designed apps ignore signals like SIGINT , which leads to frustrated users who unsuccessfully try to close the app using Ctrl+C . Some signals like the SIGKILL(9) cannot be trapped as they are meant to forcibly shut down the application when all else fails. The signal handler should be fast. The OS gives most applications just a small amount of time before it forcibly stops them. So, be mindful of this fact and do the bare minimum that is necessary. For instance, writing to a log or finishing your database transactions is good. However, doing time consuming computations or uploading large chunks of data to remote servers is not advisable. Use the right signal for the job: each signal has semantics attached to it, use them for the right job. A good example is Puma, the ruby web server which uses SIGTERM to shut down workers. Signal.trap clobbers previous signal handlers. So, use it with care. If you are writing a library, make sure you have a strong reason to use it.

» Some good examples

Look at the way well-written apps like puma, unicorn and nginx handle signals.

Some good use cases from the above examples are:

SIGQUIT for graceful shutdown (nginx).

for graceful shutdown (nginx). SIGTERM for fast shutdown (nginx).

for fast shutdown (nginx). SIGHUP for reloading configuration (nginx).

for reloading configuration (nginx). SIGUSR1 for re-opening log files (nginx).

for re-opening log files (nginx). SIGTTIN increment the number of worker processes by one (nginx, unicorn, puma).

increment the number of worker processes by one (nginx, unicorn, puma). SIGTTOU decrement the number of worker processes by one (nginx, unicorn, puma).

» Full list of signals

Signal.list will list the string identifiers and the integer values of all the signals supported by your operating system.