Capturing Ctrl-c in ruby
Solution 1
The problem is that when a Ruby program ends, it does so by raising SystemExit. When a control-C comes in, it raises Interrupt. Since both SystemExit and Interrupt derive from Exception, your exception handling is stopping the exit or interrupt in its tracks. Here's the fix:
Wherever you can, change
rescue Exception => e
# ...
end
to
rescue StandardError => e
# ...
end
for those you can't change to StandardError, re-raise the exception:
rescue Exception => e
# ...
raise
end
or, at the very least, re-raise SystemExit and Interrupt
rescue SystemExit, Interrupt
raise
rescue Exception => e
#...
end
Any custom exceptions you have made should derive from StandardError, not Exception.
Solution 2
If you can wrap your whole program you can do something like the following:
trap("SIGINT") { throw :ctrl_c }
catch :ctrl_c do
begin
sleep(10)
rescue Exception
puts "Not printed"
end
end
This basically has CtrlC use catch/throw instead of exception handling, so unless the existing code already has a catch :ctrl_c in it, it should be fine.
Alternatively you can do a trap("SIGINT") { exit! }
. exit!
exits immediately, it does not raise an exception so the code can't accidentally catch it.
Solution 3
If you can't wrap your whole application in a begin ... rescue
block (e.g., Thor) you can just trap SIGINT
:
trap "SIGINT" do
puts "Exiting"
exit 130
end
130 is a standard exit code.
Solution 4
I am using ensure
to great effect! This is for things you want to have happen when your stuff ends no matter why it ends.
Solution 5
Handling Ctrl-C cleanly in Ruby the ZeroMQ way:
#!/usr/bin/env ruby
# Shows how to handle Ctrl-C
require 'ffi-rzmq'
context = ZMQ::Context.new(1)
socket = context.socket(ZMQ::REP)
socket.bind("tcp://*:5558")
trap("INT") { puts "Shutting down."; socket.close; context.terminate; exit}
puts "Starting up"
while true do
message = socket.recv_string
puts "Message: #{message.inspect}"
socket.send_string("Message received")
end
Related videos on Youtube
Tim Snowhite
Updated on July 08, 2022Comments
-
Tim Snowhite almost 2 years
I was passed a long running legacy ruby program, which has numerous occurrences of
begin #dosomething rescue Exception => e #halt the exception's progress end
throughout it.
Without tracking down every single possible exception these each could be handling (at least not immediately), I'd still like to be able to shut it down at times with CtrlC.
And I'd like to do so in a way which only adds to the code (so I don't affect the existing behavior, or miss an otherwise caught exception in the middle of a run.)
[CtrlC is SIGINT, or SystemExit, which appears to be equivalent to
SignalException.new("INT")
in Ruby's exception handling system.class SignalException < Exception
, which is why this problem comes up.]The code I would like to have written would be:
begin #dosomething rescue SignalException => e raise e rescue Exception => e #halt the exception's progress end
EDIT: This code works, as long as you get the class of the exception you want to trap correct. That's either SystemExit, Interrupt, or IRB::Abort as below.
-
Tim Snowhite over 14 yearsWayne, would you be so kind as to add an IRB::Abort example to your list as well?
-
Tim Snowhite over 14 yearsNote that Ctrl-C in IRB sends IRB::Abort, not SIGINT. Otherwise @Logan's answer is a solution.
-
Wayne Conrad over 14 years@Tim, go find irb.rb (on my system, it's in /usr/lib/ruby/1.8/irb.rb) and find the main loop (search for @context.evaluate). Look at the rescue clauses and I think you'll understand why IRB is behaving the way it does.
-
Tim Snowhite over 14 yearsthank you. Looking at the definition for #signal_handle in irb.rb helped my understanding as well. They have a neat trick within the main loop's the exception variable binding as well. (Using the rescue clauses as a way to pick out a specific exception, then using that exception outside of the rescue bodies.)
-
defhlt almost 12 years@TimSnowhite for ruby interpreter
SIGINT
works fine for me. -
Matt Connolly over 10 yearsthrow and catch must be on the same thread, so this won't work if you want to catch the Interrupt exception on another thread.
-
James Tan about 7 yearsthese works perfect:
rescue SystemExit, Interrupt raise rescue Exception => e
-
Dorian about 7 yearsFYI, 130 is the correct exit code for Ctrl-C interrupted scripts: google.com/search?q=130+exit+code&en= (
130 | Script terminated by Control-C | Ctl-C | Control-C is fatal error signal 2, (130 = 128 + 2, see above)
) -
Narfanator almost 5 yearsPerfect! I have a wonky Sinatra server with a constantly running background thread, and this looks like what I need to kill the thread as well on a cntrl-c, without otherwise changing behavior.
-
Ron Klein over 4 yearsNice example, but I think it adds more complexity than actually needed in OP's context.