I'm running a script with an API that often times out. I'm using begin/rescue blocks to get it to redo when this happens, but want to log what is happening to the command line before I run the redo command.
begin
#...api query...
rescue ErrorClass
puts("retrying #{id}") && redo
end
Unfortunately the above script doesn't work. Only the first command is run.
I would like to force the rescue block to run multiple lines of code like so:
begin
# api query
rescue ErrorClass do ###or:# rescue ErrorClass do |e|
puts "retrying #{id}"
redo
end
but those don't work either.
I've had luck creating a separate method to run like so:
def example
id = 34314
begin
5/0
rescue ZeroDivisionError
eval(handle_zerodiv_error(id))
end
end
def handle_zerodiv_error(id)
puts "retrying #{id}"
"redo"
end
...that actually works. But it requires too many lines of code in my opinion and it uses eval which is not kosher by any means according to my mentor(s).
You are unnecessarily complicating things by using && or do. The && version does not work because puts returns nil, so by shortcut evaluation of &&, the part to follow is not evaluated. If you use || or ; instead, then it will work:
begin
...
rescue ErrorClass
puts("retrying #{id}") || redo
end
begin
...
rescue ErrorClass
puts("retrying #{id}"); redo
end
but even this is not necessary. You somehow seem to believe that you need a block within rescue to write multiple lines, but that does not make sense because you are not using a block with single line. There is no Ruby construction that requires a block only when you have multiple lines. So, just put them in multiple lines:
begin
...
rescue ErrorClass
puts("retrying #{id}")
redo
end
There is a retry built in. This example is from "The Ruby Programming Language" pg 162.
require "open-uri"
tries = 0
begin
tries +=1
open("http://www.example.com/"){|f| puts f.readlines}
rescue OpenURI::HTTPError => e
puts e.message
if (tries < 4)
sleep (2**tries) # wait for 2, 4 or 8 seconds
retry # and try again
end
end
Related
How do I execute Ruby files from another Ruby file?
class Launch
def get_program
begin
files = ["sum_of_digits", "compressed_sequence",
"shortest_repetition"]
(0...files.length).each_with_index do |index|
puts "#{index} . #{ files[index]}"
end
begin
puts "Enter program number to execute: "
puts program_number = gets.chomp.to_i
puts "loading program #{files[program_number]}"
begin
load(`ruby #{files[program_number]}.rb
#{files[program_number]}.txt`)
rescue
puts "loading error"
end
puts "do you want to continue Y/N"
answer = gets.chomp
end until answer == 'N'
rescue
puts "the file cannot be loaded ,it may be moved or not exist "
end
end
end
launch = Launch.new
launch.get_program
launch = Launch.new
launch.get_program
While executing, I am getting the output, but for only one program, and the loop is terminating. I want to execute files in a loop until the user enters "N".
In general your code isn't written in the Ruby way. This is untested but it looks about right:
class Launch
FILES = ['sum_of_digits', 'compressed_sequence', 'shortest_repetition']
def get_program
FILES.each_with_index do |fname, i|
puts "#{i} . #{fname}"
end
loop do
puts "Enter program number to execute: "
program_number = gets.to_i
file_to_load = FILES[program_number]
puts "loading program #{file_to_load}"
begin
system("ruby #{file_to_load}.rb #{file_to_load}.txt")
rescue => e
puts "loading error: #{e}"
puts "'#{file_to_load}' cannot be loaded, it may have been moved or not exist."
end
puts 'Do you want to continue Y/N'
break if gets.chomp.strip.upcase == 'N'
end
end
end
launch = Launch.new
launch.get_program
Some things to study:
block and end are used to start exception handling, not to define control loops. Well, they can, but there are better, more idiomatic, ways. loop is recommended by Matz.
You used load but I don't think that's really what you'd want to do. Instead, you should tell the OS to load and run the code in a sub-shell using system, not in the context of your currently running code.
Instead of using a bare rescue, your code should at least capture the exception using rescue => e so you can output what occurred. In "real life", AKA, production, you should be even more discerning and capture only the exceptions you expect, but that's a different discussion.
When using a begin/rescue/end, try to keep them as small as possible, at least until you're more familiar with how they work. rescue is a great way to shoot yourself in the foot, and debugging raised exceptions that could be generated by many lines of code can be a pain.
In general, when you have a list of things that's likely to change, or any variable that's more likely to change than the rest of the code, put that definition at the top of the script, or the top of the class or module definition, then reference it as a constant. That helps avoid magical dust being sprinkled through the code that has to be searched for if you want to add or delete things. Like files. Or magical dust.
I'm trying to create a rescue that if and when there is an Twitter::Error::NotFound error (such as does not exist) it will just keep going through the loop. Please help, thanks.
Below is the code,
begin
File.open("user_ids.txt") do |file|
file.each do |id|
puts client.user("#{id}").screen_name
rescue Twitter::Error::NotFound => error
next # skip this item
end
end
end
Instead of the retry method is there a a method that can skip and keep moving on to the next item in the loop?
I'm pretty sure the error.rate_limit does not apply (I copied this code from a different rescue call), is there another method to call? like error.notfound.continue_with_loop
I would like to create a rescue that if and when there is an error such as does not exist so it will just keep going through the loop. Please help, thanks.
yes next will continue and retry the next item in a loop.
retry will retry the loop with the same item.
Note: you don't have enough ends for all the do that are in that method. So I'd try:
begin
File.open("user_ids.txt") do |file|
file.each do |id|
puts client.user("#{id}").screen_name
rescue Twitter::Error::NotFound => error
sleep error.rate_limit.reset_in + 1
next # skip this item
end
end
end
Note: see how proper indentation makes it clear when you're missing an end ?
You may need to shift the begin/end block that is currently around the lot - to just be around the code that you want to rescue-from (or it'll default to the outside begin/end rather than your loop)
File.open("user_ids.txt") do |file|
file.each do |id|
begin
puts client.user("#{id}").screen_name
rescue Twitter::Error::NotFound => error
sleep error.rate_limit.reset_in + 1
next # skip this item
end
end
end
Is there a way in Ruby to have it print the __LINE__ number of code (at my script level, not required gems) it's working on if taking longer than 9 seconds (adjustable)?
For debugging I am getting it to print verbose output of what it's trying to do, where it is in the code etc., rather than silently sitting for long periods of time.
A flaky situation makes it unpredicable how far it gets before something times out, so successive advancing doesn't apply here.
EDIT
Something like a trap would work, such that:
The original line number and hopefully code get remembered (both benchmark and timeout gems lose track of __LINE__ for instance.... Maybe there is a way to push it off to another .rb file to manipulate the stack to include my file & line of interest?)
When the overtime warning prints, execution still continues as if nothing had changed.
require 'timeout'
def do_something
Timeout::timeout(9) do
sleep 10
end
rescue Timeout::Error => e
puts "Something near line #{__LINE__} is taking too long!"
# or, count backwards in method
puts "Line #{__LINE__ - 5} is taking too long!"
end
do_something
This will stop execution if the timeout block runs out of time and raise a Timeout error.
If you want to continue execution, you might do better with benchmark:
require 'benchmark'
time = Benchmark.realtime do
sleep 10
end
puts "Line #{__LINE__ - 2} is slow" if time > 9
One benchmark block can have multiple timers:
Benchmark.bm do |b|
b.report('sleeping:') { sleep 3 }
b.report('chomping:') { " I eat whitespace ".chomp }
end
See more about benchmark here:
http://ruby-doc.org/stdlib-1.9.3/libdoc/benchmark/rdoc/Benchmark.html
If you want to keep track of the line number being executed, why don't you try passing it in to a custom method like so:
def timethis(line, &block)
if Benchmark.realtime(&block) > 2
puts "Line #{line} is slow"
end
end
timethis(__LINE__) { sleep 1 }
When creating a PeriodicTimer in Ruby EventMachine, it's very inaccurate and seems to act randomly and incorrect.
I'm wondering if there is a way to fix that or if I maybe use it in a wrong way.
Below is an example.
I have a PeriodicTimer that every 10 seconds is pulling a global array (which contains mysql statements) and executes mysql commands.
When running it like that, the PeriodicTimer might end up doing this only every few minutes, or it will stop doing it completely after some time, and other fancy abnormalities.
Eventmachine::run {
EM::PeriodicTimer.new(10) {
mysql_queue = $mysql_queue.dup
$mysql_queue = []
mysql_queue.each do |command|
begin
Mysql.query(command)
rescue Exception => e
puts "Error occured.. #{e} #{command}"
end
end
}
# Here follow random tasks/loops that are adding mysql statements to $mysql_queue
100_000.times {
if ...
$mysql_queue << "INSERT INTO `...` (...) VALUES (...);"
end
}
...
# ...
}
In case you are wondering, I like to use such "Mysql Queues" because it prevents race conditions.
In practice, this is actually a small TCP/IP client which is handling multiple concurrent connections and executing mysql commands to log various actions.
What do you expect this code to do? Why do you think that timer will be ever called, if you don't pass control to EM loop?
Try splitting your 100_000 loop into smaller parts:
10_000.times do
EM.next_tick do
10.times do
if ...
$mysql_queue << "INSERT INTO `...` (...) VALUES (...);"
end
end
end
end
Using a global $ is a bad idea here, it's not quite clean if you're using threads that may access it or not.
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