ruby get fork by pid - ruby

I have a script that runs several child processes using the fork:
def my_fork s
puts "start fork #{s}, pid #{Process.pid}"
sleep s
puts "finish"
end
forks = []
5.times do |t|
forks << fork do
my_fork t+5
end
end
begin
Process.waitall
rescue Interrupt => e
puts "interrupted!"
forks.each{|fr| Process.kill 9, fr}
end
I need the ability to stop the script by pressing Ctrl+C. But pressing time, some processes may be already dead. as it can be verified?
if you do so:
forks.each{|fr| puts fr.exited?; Process.kill 9, fr}
I get an error:
undefined method `exited?' for 27520:Fixnum (NoMethodError)

The result of Fork is the PID, so rather than fr.exited? you would need to get the process status from the process with a PID of fr. Unfortunately, Ruby does not have a good way to get the process status from a PID. See Get process status by pid in Ruby
You can simply rescue the exception if you try to kill the process and it is has already completed.
instead of:
forks.each{|fr| Process.kill 9, fr}
it would be:
forks.each do |fr|
begin
Process.kill 9, fr
rescue Errno::ESRCH
puts "process #{fr} already exited"
end
end

Related

Ruby readpartial on IO.pipe won't raise EOFError

I am trying to understand how the IO.pipe in Ruby works, In the example below, I send data to the io pipe and close the stream after I finish sending the data. In a child process, I read the data using the readpartial method. According to the docs readpartial should raise EOFError when the stream is closed, but I end up with a blocked process instead, Am I understanding it right?
# ruby 2.6.3
r, w = IO.pipe
pid = fork do
loop do
puts r.readpartial(1024)
sleep 0.1
rescue EOFError
puts "End of File"
break
end
end
1000.times do |t|
w.write "xyz #{t}"
end
w.close
Process.wait(pid)
You need to close the write end of the pipe in the child as well as in the parent.
After you fork you effectively have two write ends and two read ends to the pipe. When you close the write end in the parent the write end in the child is still open, so the call to readpartial will block.
You just need to call w.close immediately after forking in the child:
pid = fork do
w.close
loop do
#...
You would normally also immediately close the read end in the parent after forking (although in this case it doesn’t really matter):
pid = fork do
#...
end
r.close

Kill linux command in ruby if timed-out

I need a solution how I can kill a system call in ruby and proceed, when this call takes too much time. If timed-out I want to set a mock. Timeout method in ruby doesn't help because it raises an error instead of moving to the end of the code.
Timeout::timeout(5) {
a = `sleep 30; echo 1`
}
puts a
Not sure if this illustrates it but for example 2 ruby files:
#slow_proceess.rb
puts 'starting.... waiting...'
10.times do
puts "slow process runing..."
sleep 1
end
puts 'done exiting'
#fast.rb
pid = Process.spawn('ruby slow_process.rb')
# after x seconds we can kill the process with
`kill -9 #{pid}`
Or in your example use instance variable to access it outside of the block and also rescue error (thanks #tadman):
begin
Timeout::timeout(5) {
#pid = Process.spawn('sleep 30; echo 1')
}
rescue Timeout::Error
puts "#{#pid} process has timed out"
end
# do stuff

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

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.

Timeout::Error exception in a loop

I have this piece of ruby code in a loop:
pid = Process.spawn("my_process_command")
begin
Timeout.timeout(16) do
`my_timeout_command`
Process.wait(pid)
end
rescue
system("clear")
puts 'Process not finished in time, killing it'
Process.kill('TERM', pid)
end
The problem is that once the Timeout::Error exception has been caught the block will be skipped and the loop does practically nothing. How can I fix this?
You need to rescue specifically for Timeout:Error since Timeout::Error is not a standard error:
pid = Process.spawn("my_process_command")
begin
Timeout.timeout(16) do
`my_timeout_command`
Process.wait(pid)
end
rescue Timeout::Error
system("clear")
puts 'Process not finished in time, killing it'
Process.kill('TERM', pid)
end

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

Resources