Timeout::Error exception in a loop - ruby

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

Related

Terminate external script from Ruby using Timeout

I have an external script running in a timeout.
def perform(command)
PTY.spawn(command) do |stdout_stderr, stdin, pid|
begin
do_stuff
rescue Errno::EIO # always thrown when process completes
do_more_stuff
ensure
Process.waitpid2(pid)
end
end
end
begin
Timeout.timeout do
perform('command_that_may_run_indefinitely')
end
rescue Timeout::Error
terminate_script_here
end
However, if the timeout times out, it needs to terminate the external script. How can I kill it?

ruby get fork by pid

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

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.

rescue Timeouts with SystemTimer

I'm using the SystemTimer gem to deal with timeout problems.
https://github.com/ph7/system-timer
I can't find a way to catch the Exception when a Timeout
begin
SystemTimer.timeout_after(10.seconds) do
# facebook api
rest_graph.fql(query)
end
rescue RestGraph::Error::InvalidAccessToken
return nil
rescue Timeout::Error
# never executed
end
But the last Exception Timeout::Error is never triggered.
Why not use Timeout, which comes with 1.9.2 and is designed to do this?
require 'timeout'
status = Timeout::timeout(5) {
# Something that should be interrupted if it takes too much time...
}
Try this: (based on your link)
class TimedOut < StandardError
end
begin
SystemTimer.timeout_after(10.seconds, TimedOut) do
# ...
end
rescue TimedOut
# ...
end

Resources