ruby: finish loop iteration before raising Interrupt - ruby

I'm looping through a lot of items and I want to periodically interrupt the loop to save and continue at a later time like this:
begin
big_list.each do |i|
# sensitive stuff
sensitive_method(i)
# other sensitive stuff
end
rescue Interrupt
# finish the current iteration
# then do something else (save)
# don't raise (print done)
end
By sensitive I mean that, if Interrupt is raised in the middle of an iteration, data will be corrupted so I need to guarantee that the iteration finishes before exiting.
Also, if another exception is raised, it should still finish the loop but raise it afterwards
EDIT:
Using the answer by mudasobwa in a test scenario:
while true
result = begin
puts "start"
sleep 1
puts "halfway"
sleep 1
puts "done\n\n"
nil
rescue Exception => e
e
end
case result
when Interrupt
puts "STOPPED"
break
when Exception then raise result
end
end
I get:
start
halfway
done
start
^C: /...
STOPPED
which is my exact problem, I need it to finish the loop (sleep, print halfway, sleep, print done) and only then break out (wrapping the puts, sleep... in a method does not help)

TL;DR: There is no way to continue the execution of the method from inside the middle of it.
big_list.each do |i|
# sensitive stuff
result = begin
sensitive_method(i)
nil
rescue Exception => e
e
end
# other sensitive stuff
case result
when Interrupt
puts "done"
break "done"
when Exception then raise result
end
end
Sidenote: you probably don’t want to rescue the topmost Exception, but some subclass that makes sense to rescue.
To make it possible to finish the chunk of operations:
operations = [
-> { puts "start" },
-> { sleep 1 },
-> { puts "halfway" },
-> { sleep 1 },
-> { puts "done\n\n" }
]
def safe_chunk(operations, index = 0)
result = operations[index..-1].each_with_index(index) do |op, idx|
begin
op.()
rescue Exception => e
safe_chunk(operations, idx) # or idx + 1
break e
end
end
result.is_a?(Array) ? nil : result
end

The Interrupt exception is raised in the main thread. If you use a worker thread to process the list it will never be interrupted. You will need a way to tell the worker thread to terminate though. Rescuing Interrupt in the main thread and setting a flag that's checked by the child can accomplish this.
BigList = (1..100)
def sensitive_method(item)
puts "start #{item}"
sleep 1
puts "halfway #{item}"
sleep 1
puts "done #{item}"
puts
end
#done = false
thread = Thread.new do
begin
BigList.each do |item|
break if #done
sensitive_method item
end
end
end
begin
thread.join
rescue Interrupt
#done = true
thread.join
end

The keyword ensure, used in rescue clauses, is available for situation such as this one, where code must be executed after an exception occurs.
[-1, 0, 1].each do |i|
begin
puts "i=#{i} before exception"
# <additional code>
n = 1/i
rescue ZeroDivisionError => e
puts "Exception: #{e}"
exit
ensure
puts "Just executed 1/#{i}"
# <additional code>
end
end
i=-1 before exception
Just executed 1/-1
i=0 before exception
Exception: divided by 0
Just executed 1/0
Notice that begin/rescue/ensure/end must be inside the loop and that the code after ensure is executed for each i regardless of whether a zero-divide exception occurs.

Related

Implement 'rescue' without 'next'

I have this example to handle errors and continue code execution:
begin
p '-' * 100
request_builder.new(env: tested_env).submit!
rescue => error
error_logs << "#{error}\n#{error.backtrace.first(5).join("\n")}"
next
end
How can I rewrite the code without next?
I found this example:
%w(1 2).each do |x|
p x
begin
raise 'something'
rescue => error
p error
end
end
=>
"1"
#<RuntimeError: something>
"2"
#<RuntimeError: something>
How can it be used to implement the code?
The next in you example has nothing to do, with a normal rescue block. It just tells to go to the next iteration. So remove your next statement and you are find.
begin
p '-' * 100
request_builder.new(env: tested_env).submit!
rescue => error
error_logs << "#{error}\n#{error.backtrace.first(5).join("\n")}"
# next # removing next, removes you error,
# you just put whatever you want in your rescue block,
# the application continues in this block
end

How do you break out of a Thread when it is in a loop?

I've run the following in irb but it doesn't break out/exit of the Thread when it is finished:
count = 0
Thread.new do
while count < 20 do
puts 'Hai'
count += 1
end
Thread.kill
end
It just hangs there in the console. Is there a way to completely exit out of the thread? From what I've researched the Thread should end after it exits the block but I couldn't find any information about this online. Thanks!
Here is the small example, May it will help you.
begin
t = Thread.new do
loop do
#bar.increment!
STDERR.flush
sleep 1
end
end
yield
STDERR.puts ' done!' unless $?.exitstatus > 0
rescue => e
STDOUT.puts "Error:"
STDOUT.puts e.message
ensure
t.kill
end

Threads in Ruby

Why does this code work (I see the output 1 2 3):
for i in 1..3
Thread.new{
puts i
}
end
However, the following code does not produce the same output (I do not see the output 1 2 3)?
for i in 1..3
Thread.new{
sleep(5)
puts i
}
end
When you hit the end of the script, Ruby exits. If you add sleep 10 after the final loop, you can see the output show up. (Albeit, as 3 each time, because the binding to i reflects the value at the end of processing, and the sleep causes a thread switch back to the loop.)
You might want something like:
threads = []
for i in 1..3
threads << Thread.new {
sleep 5
puts i
}
end
threads.map {|t| t.join }
That will wait for all the threads to terminate before exiting.

How is the value of a begin block determined?

According to The Ruby Programming Language p.164.
If a begin statement doesn't propagate an exception, then the value
of the statement is the value of the last expression evaluated in
the begin, rescue or else clauses.
But I found this behavior consistent with the begin block together with else clause and ensure clause.
Here is the example code:
def fact (n)
raise "bad argument" if n.to_i < 1
end
value = begin
fact (1)
rescue RuntimeError => e
p e.message
else
p "I am in the else statement"
ensure
p "I will be always executed"
p "The END of begin block"
end
p value
The output is:
"I am in the else statement"
"I will be always executed"
"The END of begin block"
"I am in the else statement"
[Finished]
The value is evaluated to the else clause. This is inconsistent behavior as the ensure clause is the last statement executed.
Could someone explain what's happening within the begin block?
I'd interpret the goal of the begin/rescue/else/end block as:
Execute the code in the begin section, and then the code in the else section.
If something goes wrong in the begin section, execute the rescue section instead of the else section.
So either the rescue section or the else section will be executed after trying the begin section; so it makes sense that one of them will be used as the whole block's value.
It's simply a side effect that the ensure section will always be executed.
val = begin
p "first"; "first"
rescue => e
p "fail"; "fail"
else
p "else"; "else"
ensure
p "ensure"; "ensure"
end
val # => "else"
# >> "first"
# >> "else"
# >> "ensure"
But:
val = begin
p "first"; "first"
raise
rescue => e
p "fail"; "fail"
else
p "else"; "else"
ensure
p "ensure"; "ensure"
end
val # => "fail"
# >> "first"
# >> "fail"
# >> "ensure"
I'm just guessing here, but as the purpose of a ensure block is to finalize any resources that may remain open (cleanup in other words), and so it makes sense that the logical value should be the result of the else statement. It makes sense to me that it is by design.
In this case the begin block is just a way of defining a section for which you may want to do exception handling.
Remember that else in this case runs if no exceptions occur, and ensure will run regardless of exceptions or a lack thereof.

Ruby: Continue a loop after catching an exception

Basically, I want to do something like this (in Python, or similar imperative languages):
for i in xrange(1, 5):
try:
do_something_that_might_raise_exceptions(i)
except:
continue # continue the loop at i = i + 1
How do I do this in Ruby? I know there are the redo and retry keywords, but they seem to re-execute the "try" block, instead of continuing the loop:
for i in 1..5
begin
do_something_that_might_raise_exceptions(i)
rescue
retry # do_something_* again, with same i
end
end
In Ruby, continue is spelt next.
for i in 1..5
begin
do_something_that_might_raise_exceptions(i)
rescue
next # do_something_* again, with the next i
end
end
to print the exception:
rescue
puts $!, $#
next # do_something_* again, with the next i
end

Resources