Capturing Ctrl-c in ruby

RubyExceptionCopy Paste

Ruby Problem Overview


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.

Ruby Solutions


Solution 1 - Ruby

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 - Ruby

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 - Ruby

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 - Ruby

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 - Ruby

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

Source

Solution 6 - Ruby

Perhaps the most simple solution?

Signal.trap('INT') { exit }

This is what I use, it works. Put it somewhere before a possible user interaction.

Here, a more verbose solution, to print something to STDERR and exit:

Signal.trap('INT') { abort 'Interrupted by user' }

See here for difference between exit and abort.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTim SnowhiteView Question on Stackoverflow
Solution 1 - RubyWayne ConradView Answer on Stackoverflow
Solution 2 - RubyLogan CapaldoView Answer on Stackoverflow
Solution 3 - RubyErik NomitchView Answer on Stackoverflow
Solution 4 - RubynrooseView Answer on Stackoverflow
Solution 5 - RubynorajView Answer on Stackoverflow
Solution 6 - Rubyjava.is.for.desktopView Answer on Stackoverflow