SignalException

Will kill your application if left unattended!

Ruby's SignalException class

Raised when a signal is received.

begin
  Process.kill('HUP',Process.pid)
  sleep # wait for receiver to handle signal sent by Process.kill
rescue SignalException => e
  puts "received Exception #{e}"
end

produces:

received Exception SIGHUP

Superclass

Exception

Methods

  • signo

Enemies

Honeybadger

Children

» 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:

» Using Signal.trap

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.).

» Rescuing SignalException

# 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, SignalExceptions are a great way to handle that.

» Signal handling guidelines

  1. 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.
  2. Some signals like the SIGKILL(9) cannot be trapped as they are meant to forcibly shut down the application when all else fails.
  3. 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.
  4. 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.
  5. 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).
  • SIGTERM for fast shutdown (nginx).
  • SIGHUP for reloading configuration (nginx).
  • SIGUSR1 for re-opening log files (nginx).
  • SIGTTIN 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.

#  Signals on a Linux computer

EXIT   , 0
HUP    , 1
INT    , 2
QUIT   , 3
ILL    , 4
TRAP   , 5
ABRT   , 6
IOT    , 6
BUS    , 7
FPE    , 8
KILL   , 9
USR1   , 10
SEGV   , 11
USR2   , 12
PIPE   , 13
ALRM   , 14
TERM   , 15
CHLD   , 17
CLD    , 17
CONT   , 18
STOP   , 19
TSTP   , 20
TTIN   , 21
TTOU   , 22
URG    , 23
XCPU   , 24
XFSZ   , 25
VTALRM , 26
PROF   , 27
WINCH  , 28
IO     , 29
POLL   , 29
PWR    , 30
SYS    , 31