rescue from connection reset by peer error and retry - ruby-on-rails

I am hitting an external service which does some password encryption and returns couple of things.
Now if I want to generate 50 passwords we run this function in a loop 50 times
def self.encrypt_password(password)
retries = 2
uri = URI
params = Base64.encode64(password)
uri.query = URI.encode("Source=#{params}")
begin
retries.times.each do
res = Net::HTTP.get_response(uri)
if res.is_a?(Net::HTTPSuccess)
obj = JSON.parse(res.body)
pw = Base64.decode64(obj["Data"])
ps = Base64.decode64(obj["Key"])
pws = Iconv.iconv('ascii', 'utf-16', pws)
return pwe,pws[0]
end
end
rescue
raise "Error generating pws: #{$!}"
end
end
But the problem, i am encountering is that there are occasions when the service just returns the following error in the middle of a loop and exits:
"Connection reset by Peer error"
My question is how do I rescue from that error and retry a few times without breaking the flow of the program?
Or can someone recommend alternate solutions to my problem?
NOTE: I am using ruby on rails 2 and ruby 1.8.x

Ruby has the retry method, that can be used in the rescue clause.
It just runs the current method again, so you can use a counter to limit the number of retries:
def self.encrypt_password(password)
retries = 2
uri = URI
params = Base64.encode64(password)
uri.query = URI.encode("Source=#{params}")
retries.times.each do
res = Net::HTTP.get_response(uri)
if res.is_a?(Net::HTTPSuccess)
obj = JSON.parse(res.body)
pw = Base64.decode64(obj["Data"])
ps = Base64.decode64(obj["Key"])
pws = Iconv.iconv('ascii', 'utf-16', pws)
return pwe,pws[0]
end
end
rescue SomeExceptionType
if retries > 0
retries -= 1
retry
else
raise "Error generating pws: #{$!}"
end
end
end

Related

Rails + Resque: Error not going away after update

I have a background job being run by Resque and I the process begins correctly but then I see the error:
Error
undefined method `path' for #<Hash:0x007f1900c25298>
/app/app/models/weigh_in.rb:373:in `import_without_check'
/app/app/jobs/uploads.rb:8:in `perform'
and initially this was an error but I've since updated my file and completely removed this line, pushed to heroku, restarted heroku as well as redis, yet still the error persists.
Here's the function being called in app/models/weigh_in:
def self.import_without_check(file, location_id)
error = []
success = []
options = {:key_mapping => #new_hash, :strings_as_keys => true, :keep_original_headers => true, :remove_unmapped_keys => true}
SmarterCSV.process(file, options) do |row|
hashed_row = row[0]
next if hashed_row[:scale_id].blank? || hashed_row[:scale_id].nil?
hashed_row[:unique_scale] = location_id + hashed_row[:scale_id].to_s
hashed_row = hashed_row.to_hash.except!(nil).as_json
p hashed_row
client = Client.select('id', 'name').find_by(unique_scale: hashed_row['unique_scale']) || Client.select('id', 'name').find_by(unique_mb: hashed_row['unique_scale'])
if client.nil?
error << hashed_row
next
end
hashed_row['client_id'] = client.id
program_id = client.programs.last.id
if program_id.nil? || hashed_row['client_id'].nil?
error << hashed_row
next
end
check_in = CheckIn.new(client_id: client.id, type_of_weighin: 'Standard', program_id: program_id)
unless hashed_row['date'].blank? || hashed_row['date'].nil?
p 'date', hashed_row['date']
hashed_row['date'] = Date.strptime(hashed_row["date"], "%m/%d/%y").strftime()
end
hashed_row.except!("unique_scale")
if check_in.save
hashed_row['check_in_id'] = check_in.id
end
if hashed_row['check_in_id'].nil?
error << hashed_row
next
end
weigh_in = WeighIn.new(hashed_row)
p weigh_in.valid?, weigh_in.errors.full_messages, weigh_in.errors
if weigh_in.save
success << hashed_row
end
end
return success, error
end
Is there something I need to do to make this error go away?
I solved this issue by connecting to the redis Heroku add-on via it's cli, then getting its clients, killing all connections, and restarting my heroku dynos. Once this was done, my changes were recorded:
$ heroku redis:cli
21579> client list # Returns clients
21579> client kill 10.159.107.79:43434 # use number from addr property returned with client list command
21579> quit # Exit cli
$ heroku restart
I hope this helps someone else

How to deal with timeout in currency conversion service?

I just created this little currency converter for my Rails 4 app:
module Currency
def self.get_exchange_rate(from_curr = "EUR", to_curr = "USD")
if from_curr == to_curr
result = 1
else
begin
amount = 1
url = "http://www.google.com/finance/converter?a=#{amount}&from=#{from_curr}&to=#{to_curr}"
doc = Nokogiri::HTML(open(url))
result_span = doc.css('span.bld').text
result = result_span.tr('^0-9.', '')
rescue => e
puts e
result = 1
end
end
result
end
end
I haven't done this a lot, so my question would be: How can I deal with the (unlikely) event that Google Currency times out or is not available for some reason?
In that case I would like my result to be 1. How can this be achieved?
Thanks for any suggestions.
You can simply rescue any error that Nokogiri might raise (or OpenURI) like so:
require 'nokogiri'
require 'open-uri'
def currency(a)
Nokogiri::HTML(open(a))
rescue => e
puts e
1 # default value when error is raised
end
puts currency('https://www.somedomainthatdoesntexist.com')
That will print the backtrace of the error and then return the number 1
getaddrinfo: nodename nor servname provided, or not known
1
For a custom timeout actions you can use Timeout module
http://ruby-doc.org/stdlib-2.1.2/libdoc/timeout/rdoc/Timeout.html
It'd be worth looking at this answer to see how to get nokogiri to timeout : Adjusting timeouts for Nokogiri connections
The question makes use of the timeout module which is an option but I think it is better to get the connection to time out as per the accepted answer.
All that will mean that you'll get a Timeout::Error exception raised if the call times out which you then need to handle:
begin
doc = ... use nokogiri ...
result_span = doc.css('span.bld').text
result = result_span.tr('^0-9.', '')
rescue Timeout::Error
result = 1
end
result

Can I automatically re-run a method if a timeout error occurs?

We have an application that makes hundreds of API calls to external services. Sometimes some calls take too much time to respond.
I am using the rake_timeout gem to find time consuming process, so, Timeout::Error will be thrown whenever some request is taking too long to respond. I am rescuing this error and doing a retry on that method:
def new
#make_one_external_service_call = exteral_api_fetch1(params[:id])
#make_second_external_call = exteral_api_fetch1(#make_one_external_service_call)
#Below code will be repeated in every method
tries = 0
rescue Timeout::Error => e
tries += 1
retry if tries <= 3
logger.error e.message
end
This lets the method fully re-run it. This is very verbose and I am repeating it every time.
Is there any way to do this so that, if the Timeout:Error occurrs, it will retry that method automatically three times?
I have a little module for that:
# in lib/retryable.rb
module Retryable
# Options:
# * :tries - Number of tries to perform. Defaults to 1. If you want to retry once you must set tries to 2.
# * :on - The Exception on which a retry will be performed. Defaults to Exception, which retries on any Exception.
# * :log - The log level to log the exception. Defaults to nil.
#
# If you work with something like ActiveRecord#find_or_create_by_foo, remember to put that call in a uncached { } block. That
# forces subsequent finds to hit the database again.
#
# Example
# =======
# retryable(:tries => 2, :on => OpenURI::HTTPError) do
# # your code here
# end
#
def retryable(options = {}, &block)
opts = { :tries => 1, :on => Exception }.merge(options)
retry_exception, retries = opts[:on], opts[:tries]
begin
return yield
rescue retry_exception => e
logger.send(opts[:log], e.message) if opts[:log]
retry if (retries -= 1) > 0
end
yield
end
end
and than in your model:
extend Retryable
def new
retryable(:tries => 3, :on => Timeout::Error, :log =>:error) do
#make_one_external_service_call = exteral_api_fetch1(params[:id])
#make_second_external_call = exteral_api_fetch1(#make_one_external_service_call)
end
...
end
You could do something like this:
module Foo
def self.retryable(options = {})
retry_times = options[:times] || 10
try_exception = options[:on] || Exception
yield if block_given?
rescue *try_exception => e
retry if (retry_times -= 1) > 0
raise e
end
end
Foo.retryable(on: Timeout::Error, times: 5) do
# your code here
end
You can even pass multiple exceptions to "catch":
Foo.retryable(on: [Timeout::Error, StandardError]) do
# your code here
end
I think what you need is the retryable gem.
With the gem, you can write your method like below
def new
retryable :on => Timeout::Error, :times => 3 do
#make_one_external_service_call = exteral_api_fetch1(params[:id])
#make_second_external_call = exteral_api_fetch1(#make_one_external_service_call)
end
end
Please read the documentation for more information on how to use the gem and the other options it provides
you could just write a helper-method for that:
class TimeoutHelper
def call_and_retry(tries=3)
yield
rescue Timeout::Error => e
tries -= 1
retry if tries > 0
Rails.logger.error e.message
end
end
(completely untested) and call it via
TimeoutHelper.call_and_retry { [your code] }

code to ping websites works sometimes

I'm testing out a piece of code to ping a bunch of websites I own on a regular basis, to make sure they're up.
I'm using rails and so far I have this hideous test action that I'm using to try it out (see below).
The problem though, is that sometimes it works, and other times it won't ... sometimes it runs through the code just fine, other times, it seems to completely ignore the begin/rescue block ...
a. I need help figuring out what the problem is
b. And refactoring this to make it look respectable.
Your help is much appreciated.
edit 1: Here is the updated code, sorry it took so long, pastie.org was down since yesterday http://pastie.org/927201
Its still doing the same thing ... skipping the begin block (because it only updates up_check_time) ... however if one of the sites times out, it actually updates everything (check_msg, code etc) correctly ... confusing, yeah?
require 'net/http'
require 'uri'
def ping
#sites = NewsSource.all
#sites.each do |site|
if site.uri and !site.uri.empty?
uri = URI.parse(site.uri)
response = nil
path = uri.path.blank? ? '/' : uri.path
path = uri.query.blank? ? path : "#{path}?#{uri.query}"
begin
Net::HTTP.start(uri.host, uri.port) {|http|
http.open_timeout = 30
http.read_timeout = 30
response = http.head(path)
}
if response.code.eql?('200') or response.code.eql?('301') or response.code.eql?('302')
site.up = true
else
site.up = false
end
site.up_check_msg = response.message
site.up_check_code = response.code
rescue Errno::EBADF
rescue Timeout::Error
site.up = false
site.up_check_msg = 'timeout'
site.up_check_code = '408'
end
site.up_check_time = 0.seconds.ago
site.save
end
end
end
You currently have an empty rescue block for Errno::EBADF so if that exception is raised then you will not be setting site.up to false.
Also, a couple of other minor improvements:
Instead of if site.uri and !site.uri.empty? you can use:
next if site.uri.nil? or site.uri.empty?
to skip that iteration of the each loop and avoid indenting the code by an additional level.
And:
if response.code.eql?('200') or response.code.eql?('301') or response.code.eql?('302')
site.up = true
else
site.up = false
end
can be written more concisely:
site.up = ['200', '301', '302'].include? response.code
If you tidy up the code with some of these tips then it might help narrow down the problem.
Here's a snippet from one of my programs, maybe it helps:
urls.each_with_index do |url, idx|
print "Processing URL #%04d: " % (idx+1)
uri = URI.parse(url)
response = nil
begin
Net::HTTP.start(uri.host, uri.port) do |http|
response = http.head(uri.path.size > 0 ? uri.path : "/")
end
rescue => e
puts "#{e.message} - #{url}"
next
end
# handle redirects
if response.is_a?(Net::HTTPRedirection)
new_uri = URI.parse(response['location'])
puts "URI redirects to #{new_uri}"
next
end
puts case response.code
when '200' then ...
when '404' then ...
else ...
end
end
The only thing that I can think of is that you are getting some other exception in your begin block. Since you are only explicitly rescuing Errno::EBADF, Timeout::Error it would appear that your begin and rescue got skipped. You might be able to verify this by getting rid of Errno::EBADF, Timeout::Error and just having a plain rescue, then put the following in your rescue block
logger.info(">>Exception was: "+$!)
Then look in your logs to see what exceptions you are getting.
If you are monitoring your servers why not use Nagios? it's free and also has some Ruby support, Here and Here.
EDIT:
Ruby GEM: http://hobodave.com/2010/01/10/simple-nagios-probes-in-ruby/

read_timeout in Nokogiri?

I'm fetching some weather data from an online xml doc using Nokogiri, and I would like to set up a timeout for graceful recovery in case the source can't be reached...
My google searches show several possible methods for open-uri and Net::HTTP, but none specific to Nokogiri. My attempts to use those methods are failing (not too surprisingly):
begin
currentloc = ("http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=" + #destination.weatherloc)
currentloc.read_timeout = 10 #
doc = Nokogiri::XML(open(currentloc))
rescue Timeout::Error
return "Current weather for this location not available: request timed out."
end
returns "NoMethodError", and:
begin
currentloc = ("http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=" + #destination.weatherloc)
doc = Nokogiri::XML(open(currentloc), :read_timeout => 10)
rescue Timeout::Error
return "Current weather for this location not available: request timed out."
end
returns "TypeError can't convert Hash into String"
Does Nokogiri support this kind of method (and if so... how?), or should I be looking at some other solution?
Thanks.
You can use the timeout module :
require 'open-uri'
require 'nokogiri'
require 'timeout'
begin
timeout(10) do
result = Nokogiri::XML(open(currentloc))
end
rescue Timeout::Error
return "Current weahter..."
end

Resources