Signals In Ruby / "rescue Exception" considered harmful
Yesterday we had an issue with the different behaviour of “kill ” and “kill -9 ” and in the process I had to refresh my knowledge of Unix signals, learn how you handle them in Ruby and properly learn Rubys exception hierarchy.
To -9 or not to -9?
The unix kill command is perhaps strangely named as it actually sends signals to processes (see “man signal” for a full list). It defaults to sending SIGTERM to the process and the application writer can decide how to treat it by “trapping” it, allowing for a safe shutdown or debug dumps etc. “kill -9” sends SIGKILL and the signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored by your programs. I think in the first instance you should just use “kill”, give the app the chance to do the right thing then get -9 on its ass if you need to.
Catching signals in Ruby
puts "I have PID #{Process.pid}"
Signal.trap("USR1") {puts "prodded me"}
loop do
sleep 5
puts "doing stuff"
end
Is about the simplest code that will trap the “USR1” signal (which you can send with “kill -USR1 ”). The USR1 and USR2 signals are left free for you to use however you wish in your applications.
If you look at the image below you can see that it responds to the USR1 signal I send it and kill (ie sending SIGTERM) works also.
The following two code snippets are the same except one takes the default and the other catches Exception (ie any exception)
puts "I have PID #{Process.pid}"
Signal.trap("USR1") {puts "prodded me"}
loop do
begin
puts "doing stuff"
sleep 10
rescue => e
puts e.inspect
end
end
So that still works as before and errors in our “do stuff” loop would get caught.
puts "I have PID #{Process.pid}"
Signal.trap("USR1") {puts "prodded me"}
loop do
begin
puts "doing stuff"
sleep 10
rescue Exception => e
puts e.inspect
end
end
This fails though. You can see that SIGTERM no longer works and CTRL-C from the terminal does not work also. This is because we are catching the SignalException when we do “rescue Exception”. Kill -9 worked though, as it will kill any application as the signal cannot be caught.
Rubys Exception Heirachy
The full exception heirachy (from the excellent cheat gem) is
exceptions:
Exception
NoMemoryError
ScriptError
LoadError
NotImplementedError
SyntaxError
SignalException
Interrupt
Timeout::Error # require 'timeout' for Timeout::Error
StandardError # caught by rescue if no type is specified
ArgumentError
IOError
EOFError
IndexError
LocalJumpError
NameError
NoMethodError
RangeError
FloatDomainError
RegexpError
RuntimeError
SecurityError
SocketError
SystemCallError
SystemStackError
ThreadError
TypeError
ZeroDivisionError
SystemExit
fatal
I think you should only catch StandardError or its children, possibly some of its siblings and avoid catching Exception as you probably dont want to change how the process deals with signals (you could trap them if you need to)