How can I exit a ruby program on strg-c if a SystemExit exception is being catched - ruby

The code which I can not interrupt by using strg-c (Ctrl-C) :
orig_std_out = STDOUT.clone
orig_std_err = STDERR.clone
STDOUT.reopen('/dev/null', 'w')
STDERR.reopen('/dev/null', 'w')
name = cookbook_name(File.join(path, 'Metadata.rb'))
error = 0
begin
::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"])
rescue SystemExit
error = 1
end
.
.
.
In my understanding this behaviour would be reasonable if I would rescue Exception, but in this case I am basically catching siblings which only share their parent exception Exception.
I have already tried to rescue the exception Interrupt and SignalException explicitly.
EDIT1: In the hope of clarifying my question I added the following code which i tried:
begin
::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"])
rescue SystemExit => e
msg1 = e.message
error = 1
rescue Interrupt
msg2 = "interrupted"
end
In both cases - SystemExit thrown by Knife.run and thrown by Ctrl-C - e.message returns "exit". This does not only mean, that Ctrl-C throws a SystemExit whereas I am expecting it to throw an Interrupt, but also that the error message is the same.
I guess that I have got a major misunderstanding in how ruby works there, since I am not very familiar with ruby.
EDIT2: Further testing revealed that some Ctrl-C interrupts are rescued by rescue Interrupt. Is it possible that the command ::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"]), which takes about 3-5 seconds to run, creates some kind of subprocess which responds to a Ctrl-C, but always closes with a SystemExit and that rescue Interruptonly works when it is interrupted just the moment this subprocess is not running? If this is the case, how am I going to be able to Interrupt the whole program?
EDIT3: I initially wanted to attach all the methods which get called on calling Knife.run, however, this would have been too many LoC, although I think my guess that a subcommand is executed was right. The chef gem code can be found here. Thus, the following excerpt is only the part which is the problematic one in my opinion:
rescue Exception => e
raise if raise_exception || Chef::Config[:verbosity] == 2
humanize_exception(e)
exit 100
end
Which leads to the question: How can I catch a Ctrl-C which is already rescued by a subcommand?

I have done gem install chef. Now I try another solution, replacing only run_with_pretty_exceptions, but don't know which require to put in the script. I did this :
require 'chef'
$:.unshift('Users/b/.rvm/gems/ruby-2.3.3/gems/chef-13-6-4/lib')
require 'chef/knife'
But then :
$ ruby chef_knife.rb
WARNING: No knife configuration file found
ERROR: Error connecting to https://supermarket.chef.io/api/v1/cookbooks/xyz, retry 1/5
...
So, without the whole infrastructure, I can't test the following solution. The idea is that in Ruby you can reopen an existing class and replace a method defined elsewhere. I have to leave you check it :
# necessary require of chef and knife ...
class Chef::Knife # reopen the Knife class and replace this method
def run_with_pretty_exceptions(raise_exception = false)
unless respond_to?(:run)
ui.error "You need to add a #run method to your knife command before you can use it"
end
enforce_path_sanity
maybe_setup_fips
Chef::LocalMode.with_server_connectivity do
run
end
rescue Exception => e
raise if e.class == Interrupt # <---------- added ********************
raise if raise_exception || Chef::Config[:verbosity] == 2
humanize_exception(e)
exit 100
end
end
name = cookbook_name(File.join(path, 'Metadata.rb'))
error = 0
begin
::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"])
rescue SystemExit => e
puts "in rescue SystemExit e=#{e.inspect}"
error = 1
rescue Interrupt
puts 'in rescue Interrupt'
end
raise if e.class == Interrupt will re-raise Interrupt if it is one.
Normally I run ruby -w to display diagnostics, which would be like this :
$ ruby -w ck.rb
ck.rb:9: warning: method redefined; discarding old run_with_pretty_exceptions
ck.rb:4: warning: previous definition of run_with_pretty_exceptions was here
Unfortunately there are so many uninitialized variables and circular require warnings in this gem that this option produces un unmanageable output.
The drawback of this solution is that you have to keep a documentation track of this change, and in case of Chef's release change, somebody has to verify if the code of run_with_pretty_exceptions has changed.
Please give me a feedback.
===== UPDATE =====
There is a less intrusive solution, which consists in defining an exit method in Chef::Knife.
When you see exit 100, i.e. a message without receiver, the implicit receiver is self, it is equivalent to self.exit 100. In our case, self is the object created by instance = subcommand_class.new(args), and which is the receiver in instance.run_with_pretty_exceptions.
When a message is sent to an object, the message search mechanism starts looking in the class of this object. If there is no method with this name in the class, the search mechanism looks in included modules, then the superclass, etc until it reaches Object, the default superclass of Chef::Knife. Here it finds Object#exit and executes it.
After defining an exit method in Chef::Knife, the message search mechanism, when it encounters exit 100 with an instance of Chef::Knife as implicit receiver, will first find this local method and execute it. By previously aliasing the original Object#exit, it is still possible to call the original Ruby method which initiates the termination of the Ruby script. This way the local exit method can decide either to call the original Object#exit or take other action.
Following is a complete example which demonstrates how it works.
# ***** Emulation of the gem *****
class Chef end
class Chef::Knife
def self.run(x)
puts 'in original run'
self.new.run_with_pretty_exceptions
end
def run_with_pretty_exceptions
print 'Press Ctrl_C > '
gets
rescue Exception => e
puts
puts "in run_with_pretty...'s Exception e=#{e.inspect} #{e.class}"
raise if false # if raise_exception || Chef::Config[:verbosity] == 2
# humanize_exception(e)
puts "now $!=#{$!.inspect}"
puts "about to exit, self=#{self}"
exit 100
end
end
# ***** End of gem emulation *****
#----------------------------------------------------------------------
# ***** This is what you put into your script. *****
class Chef::Knife # reopen the Knife class and define one's own exit
alias_method :object_exit, :exit
def exit(p)
puts "in my own exit with parameter #{p}, self=#{self}"
puts "$!=#{$!.inspect}"
if Interrupt === $!
puts 'then about to raise Interrupt'
raise # re-raise Interrupt
else
puts 'else about to call Object#exit'
object_exit(p)
end
end
end
begin
::Chef::Knife.run([])
rescue SystemExit => e
puts "in script's rescue SystemExit e=#{e.inspect}"
rescue Interrupt
puts "in script's rescue Interrupt"
end
Execution. First test with Ctrl-C :
$ ruby -w simul_chef.rb
in original run
Press Ctrl_C > ^C
in run_with_pretty...'s Exception e=Interrupt Interrupt
now $!=Interrupt
about to exit, self=#<Chef::Knife:0x007fb2361c7038>
in my own exit with parameter 100, self=#<Chef::Knife:0x007fb2361c7038>
$!=Interrupt
then about to raise Interrupt
in script's rescue Interrupt
Second test with a hard interrupt.
In one terminal window :
$ ruby -w simul_chef.rb
in original run
Press Ctrl_C >
In another terminal window :
$ ps -ef
UID PID PPID C STIME TTY TIME CMD
0 1 0 0 Fri01PM ?? 0:52.65 /sbin/launchd
...
0 363 282 0 Fri01PM ttys000 0:00.02 login -pfl b /bin/bash -c exec -la bash /bin/bash
501 364 363 0 Fri01PM ttys000 0:00.95 -bash
501 3175 364 0 9:51PM ttys000 0:00.06 ruby -w simul_chef.rb
...
$ kill 3175
Back in the first terminal :
in run_with_pretty...'s Exception e=#<SignalException: SIGTERM> SignalException
now $!=#<SignalException: SIGTERM>
about to exit, self=#<Chef::Knife:0x007fc5a79d70a0>
in my own exit with parameter 100, self=#<Chef::Knife:0x007fc5a79d70a0>
$!=#<SignalException: SIGTERM>
else about to call Object#exit
in script's rescue SystemExit e=#<SystemExit: exit>
Considering the code you originally posted, all you have to do is inserting at the beginning, but after the necessary require :
class Chef::Knife # reopen the Knife class and define one's own exit
alias_method :object_exit, :exit
def exit(p)
if Interrupt === $!
raise # re-raise Interrupt
else
object_exit(p)
end
end
end
So there is no need to touch the original gem.

The following code shows how I am able to interrupt after all:
interrupted = false
trap("INT") { interrupted = true} #sent INT to force exit in Knife.run and then exit
begin
::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"]) #exits on error and on interrupt with 100
if interrupted
exit
end
rescue SystemExit => e
if interrupted
exit
end
error = 1
end
The drawback still is, that I am not exactly able to interrupt the Knife.run, but only able to trap the interrupt and check after that command whether an interrupt was triggered. I found no way to trap the interrupt and "reraise" it at the same time, so that I am at least able to force an exit out of Knife.run which I can then exit manually.

Related

Trap Ctrl-D in a Ruby Script with ARGF

I am currently using ARGV.gets to capture user input from the command line. I want to allow Ctrl-D terminate the script, but don't know how to do this using Signal.trap or through error handling. I tried to find a list of trap codes for something like Ctrl-D but was unable to find anything I was looking for. Likewise, rescuing Exception doesn't work because Ctrl-D doesn't raise an exception. Is there a trap code for Ctrl-D or any other way to detect this?
For example...
I am currently able to detect Ctrl-C by trapping...
# Trap ^C
Signal.trap("INT") {
# Do something
exit
}
or error handling...
def get_input
input = ARGF.gets
input.strip!
rescue SystemExit, Interrupt => e
# If we get here, Ctrl-C was encountered
end
However, I haven't been able to trap or detect Ctrl-D.
ARGF in just a special case of stream. Ctrl + D is just end of input.
With this in mind use method ARGF.eof?. Link to documentation
I am unsure of your use case but I am assuming you are intending to do something before the script exits. If so then your best bet is probably a bit easier than signal trapping. The Kernel Module actually offers you an #at_exit method that will be executed just prior to the program actually exiting.
Usage: (from Kernel#at_exit Docs)
def do_at_exit(str1)
at_exit { print str1 }
end
at_exit { puts "cruel world" }
do_at_exit("goodbye ")
exit
"produces:"
goodbye cruel world
as you can see you can define multiple handlers which will be executed in reverse order when the program exits.
Since Kernel is included in Object you can handle Object specifics as well like
class People
at_exit {puts "The #{self.name} have left"}
end
exit
# The People have left
or even on instances
p = People.new
p.send(:at_exit, &->{puts "We are leaving"})
# We are leaving
# The People have left
Additionally for more specific Object based implementations you can take a look at ObjectSpace.define_finalizer.
example of usage:
class Person
def self.finalize(name)
proc {puts "Goodbye Cruel World -#{name}"}
end
def initialize(name)
#name = name
ObjectSpace.define_finalizer(self, self.class.finalize(#name))
end
end
Usage:
p = Person.new("engineersmnky")
exit
# Goodbye Cruel World -engineersmnky
This may not be specifically what you want as this will fire when an Object is garbage collected as well (not great for ephemeral objects) but if you have objects that should exist throughout the entire application this could still be used similar to an at_exit . Example
# requiring WeakRef to allow garbage collection
# See: https://ruby-doc.org/stdlib-2.3.3/libdoc/weakref/rdoc/WeakRef.html
require 'weakref' #
p1 = Person.new("Engineer")
p2 = Person.new("Engineer's Monkey")
p2 = WeakRef.new(p2)
GC.start # just for this example
# Goodbye Cruel World -Engineer's Monkey
#=> nil
p2
#=> WeakRef::RefError: Invalid Reference - probably recycled
exit
# Goodbye Cruel World -Engineer
As you can see the defined finalizer for p2 fired because the Person was gc'd but the program has not exited yet. p1's finalizer waited until exit to fire because it retained its reference throughout the application.

Timeout within a popen works, but popen inside a timeout doesn't?

It's easiest to explain in code:
require 'timeout'
puts "this block will properly kill the sleep after a second"
IO.popen("sleep 60") do |io|
begin
Timeout.timeout(1) do
while (line=io.gets) do
output += line
end
end
rescue Timeout::Error => ex
Process.kill 9, io.pid
puts "timed out: this block worked correctly"
end
end
puts "but this one blocks for >1 minute"
begin
pid = 0
Timeout.timeout(1) do
IO.popen("sleep 60") do |io|
pid = io.pid
while (line=io.gets) do
output += line
end
end
end
rescue Timeout::Error => ex
puts "timed out: the exception gets thrown, but much too late"
end
My mental model of the two blocks is identical:
So, what am I missing?
edit: drmaciver suggested on twitter that in the first case, for some reason, the pipe socket goes into non-blocking mode, but in the second it doesn't. I can't think of any reason why this would happen, nor can I figure out how to get the descriptor's flags, but it's at least a plausible answer? Working on that possibility.
Aha, subtle.
There is a hidden, blocking ensure clause at the end of the IO#popen block in the second case. The Timeout::Error is raised raised timely, but you cannot rescue it until execution returns from that implicit ensure clause.
Under the hood, IO.popen(cmd) { |io| ... } does something like this:
def my_illustrative_io_popen(cmd, &block)
begin
pio = IO.popen(cmd)
block.call(pio) # This *is* interrupted...
ensure
pio.close # ...but then control goes here, which blocks on cmd's termination
end
and the IO#close call is really more-or-less a pclose(3), which is blocking you in waitpid(2) until the sleeping child exits.
You can verify this like so:
#!/usr/bin/env ruby
require 'timeout'
BEGIN { $BASETIME = Time.now.to_i }
def xputs(msg)
puts "%4.2f: %s" % [(Time.now.to_f - $BASETIME), msg]
end
begin
Timeout.timeout(3) do
begin
xputs "popen(sleep 10)"
pio = IO.popen("sleep 10")
sleep 100 # or loop over pio.gets or whatever
ensure
xputs "Entering ensure block"
#Process.kill 9, pio.pid # <--- This would solve your problem!
pio.close
xputs "Leaving ensure block"
end
end
rescue Timeout::Error => ex
xputs "rescuing: #{ex}"
end
So, what can you do?
You'll have to do it the explicit way, since the interpreter doesn't expose a way to override the IO#popen ensure logic. You can use the above code as a starting template and uncomment the kill() line, for example.
In the first block, the timeout is raised in the child, killing it and returning control to the parent. In the second block, the timeout is raised in the parent. The child never gets the signal.
See io.c https://github.com/ruby/ruby/blob/trunk/io.c#L6021
and timeout.rb https://github.com/ruby/ruby/blob/trunk/lib/timeout.rb#L51

Which system signal is sent to a ruby program when an exception is raised and the program stops execution?

Any time my program stops execution (either when shut down by cmd-c or when it encounters an exception), I want to take a few actions to shut down properly.
When I do cmd-c, I receive the signal TERM. What signal is sent when the program encounters an exception that is raised? How do I trap this with Signal.trap(...)?
You could wrap your code in a begin-ensure-end block. It would catch exceptions and CTRL-C. (You could add a rescue clause before the ensure).
begin
sleep 10 #try CTRL-C here
raise "kaboom" #RuntimeError
ensure
puts "This must be printed no matter what."
end
An exception is not a signal. The Ruby interpreter handles exceptions all in user code; there's nothing to trap.
If you want to handle exceptions, you need to do so in a rescue block.
You can't catch the exception as a signal, but you can do something when it's raised using the 'EXIT' signal:
Signal.trap('EXIT') do
puts "Terminating..."
shutdown()
end
However, I just stated that you can do this; you really should use begin and rescue.
The point wit exceptions is not trapping the signal via Signal.trap but rather wrapping the code that may raise an exception in a begin-rescue-end block. You have more Options though:
begin
# here goes the code that may raise an exception
rescue ThisError
# this code is executed when 'ThisError' was raised
rescue ThatError, AnotherError
# this code is executed when 'ThatError' or 'AnotherError' was raised
rescue
# this code is executed when any other StandardError was raised
else
# this code is executed when NO exception was raised
ensure
# this code is always executed
end
Here are some bit more practical examples of how to use this:
def compute_something(x,y)
raise ArgumentError, 'x must not be lower than 0' if x < 0
x/y + y
end
begin
compute_something(-10,5)
rescue ArgumentError
puts "some argument is erroneous!"
end
puts "---"
x=100
y=0
begin
compute_something(x,y)
rescue ZeroDivisionError
puts "division by zero! trying to fix that..."
y=1
retry
else
puts "everything fine!"
end
puts "---"
begin
compute_something(1)
rescue => e
puts "the following error occured:"
puts e
end
puts "---"
begin
exit
ensure
puts "i am always called!"
end
this outputs:
some argument is erroneous!
---
division by zero! trying to fix that...
everything fine!
---
the following error occured:
wrong number of arguments (1 for 2)
---
i am always called!
As an alternative to the above solutions, you could look into the at_exit method.

In Ruby, how to trap a signal multiple times?

I have a program that will trap Ctrl + c, but it can be trapped just once. When you type exit to exit from the irb session, the signal generated by Ctrl + c can't be trapped again. Any ideas? This is the program:
require 'irb'
#trap "INT" do
# IRB.start
#end
Signal.trap("INT") { IRB.start }
count = 0
loop do
count += 1
puts count
puts "Value = #{#value}" if defined? #value
sleep 1
end
The problem is that IRB sets its own handler for sigint when you invoke it, which overrides your handler. To fix this, you can reset your handler again, after IRB is finished, like this:
def set_trap
Signal.trap("INT") do
IRB.start
set_trap
end
end
set_trap
This will cause a bunch of warning when you invoke IRB the second time though, but that's a general problem with invoking IRB multiple times.

Capturing Ctrl-c in ruby

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

Resources