Inconsistent Timeout::timeout and rescue Timeout::Error behavior - ruby-on-rails

I'm using Timeout::timeout(1) for a process that takes longer than 1 second, though it only occasionally triggers a timeout. When it does, rescue captures it in different ways each time. Here's a sample of my code:
require 'timeout'
...
begin
status = Timeout::timeout(1) {
open(file_url) do |foo|
feed = RSS::Parser.parse(foo)
some_method_call(arg1, arg2)
#other stuff
end
}
rescue Timeout::Error
Rails.logger.debug "Timeout"
return nil
rescue Exception => ex
Rails.logger.debug "EXCEPTION - #{ex.message}"
return nil
end
Here are the three scenarios I encounter with the same input:
Process runs to completion and takes longer than 60 seconds
Process times out and hangs, printing only execution expired in development.log
Process times out, is rescued properly, prints "Timeout" in development.log, and returns nil
Why is this so inconsistent?
UPDATE
After reducing the timeout to 0.0001s, the process is timing out consistently and as expected. It seems that the open(file_url) block was opening faster than 1 second, and despite everything within the block taking more than 1 second, the Timeout was only triggered if the opening itself took longer than 1 second.
This however did not explain the execution expired exception. To test this, I moved the Timeout::timeout(0.0001) to within the open block. The code looks like the following:
require 'timeout'
...
begin
open(file_url) do |foo|
status = Timeout::timeout(0.0001) do
begin
feed = RSS::Parser.parse(foo)
some_method_call(arg1, arg2)
#other stuff
rescue Timeout::Error
Rails.logger.debug "Timeout 2"
rescue Exception => ex
Rails.logger.debug "EXCEPTION 2 - #{ex.message}"
end
end
end
rescue Timeout::Error
Rails.logger.debug "Timeout"
return nil
rescue Exception => ex
Rails.logger.debug "EXCEPTION - #{ex.message}"
return nil
end
Now, I'm consistently receiving the output EXCEPTION 2 - execution expired. Why is it that the Timeout::Error is not being triggered here?

Your inner
rescue Exception => ex
Rails.logger.debug "EXCEPTION 2 - #{ex.message}"
end
keeps the outer timeoutblock from raising Timeout::Error.
Removing that rescuestatement should do the trick.
If you really need to catch any exception, replace it with:
rescue StandardError => ex
Rails.logger.debug "EXCEPTION 2 - #{ex.message}"
end

Internally (within the Timeout block) Timeout does not use Timeout::Error. If it did, then every garden-variety rescue would catch it, and you don't want that. So it creates a new Exception and uses that, so that it hopefully blows through all normal error handling and actually makes the code stop running.
Check out the timeout.rb code in ruby200/lib/ruby/2.0.0. It's quite short, and pretty informative.
In particular, you can pass your own Exception in as the second parameter of Timeout::timeout, and Timeout will use that. So you can catch it inside your code, if you want.
Note that Logger currently traps all exceptions that happen while writing, and doesn't re-raise, so it breaks Timeout. I've filed a bug report.

Related

How to write RSpec test cases for TimeOutError Exception

I have written retry logic for sending mails and trying to cover test cases for it.
def send_mail(mail,log_obj)
begin
attempts ||= 1
mail.deliver_now
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Net::OpenTimeout, Net::ReadTimeout => e
CommonLogger.input_log(log_obj,"Timeout: #{e} (attempt ##{ attempts })")
if (attempts += 1) <= 4
sleep(1)
Rails.logger.info ("Retrying...")
retry
else
Rails.logger.error("Retry attempts exceeded.")
raise e
end
end
end
You can use and_raise to raise errors
https://relishapp.com/rspec/rspec-mocks/docs/configuring-responses/raising-an-error
allow(mail).to receive(:deliver_now).and_raise(Net::OpenTimeout)
allow_any_instance_of(MailClass).to receive(:deliver_now).and_raise(Net::OpenTimeout)

Rescue not capturing failure

I'm missing something here, but for some reason my begin then rescue Ruby code isn't capturing this error:
#<ActiveResource::ResourceInvalid: Failed. Response code = 422. Response message = Unprocessable Entity.>
This is my code:
begin
ShopifyAPI::CarrierService.create(with some arguments)
rescue StandardError => e
pp e
end
It doesn't ever capture it. In my rescue section I've tried the above but also:
rescue Exception => e
rescue ActiveResource::Errors => e
All with no luck. Where did I go astray?
Thanks
EDIT:
This is the full error, it not really anymore info, but here goes:
#<ShopifyAPI::CarrierService:0x0000000357a0a0
#attributes=
{"name"=>"XXXX",
"callback_url"=>
"https://XX-XX-XX-XX.c9users.io/receive_rate_request",
"format"=>"json",
"service_discovery"=>"true",
"carrier_service_type"=>"api"},
#errors=
#<ActiveResource::Errors:0x00000003578930
#base=#<ShopifyAPI::CarrierService:0x0000000357a0a0 ...>,
#messages={:base=>["you already have XXX set up for this shop"]}>,
#persisted=false,
#prefix_options={},
#remote_errors=
#<ActiveResource::ResourceInvalid: Failed. Response code = 422. Response message = Unprocessable Entity.>,
#validation_context=nil>
That's it!
Because it is not raising an exception, If you want to raise the exception when the response is false, you may have to use create with bang
begin
ShopifyAPI::CarrierService.create!(with some arguments)
rescue StandardError => e
pp e
end
According to the ActiveResource code (lib/active_resource/base.rb):
# <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
# following HTTP response codes will also result in these exceptions:
#
# * 200..399 - Valid response. No exceptions, other than these redirects:
# * 301, 302, 303, 307 - ActiveResource::Redirection
# * 400 - ActiveResource::BadRequest
# * 401 - ActiveResource::UnauthorizedAccess
# * 403 - ActiveResource::ForbiddenAccess
# * 404 - ActiveResource::ResourceNotFound
...
# * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
So it indicates that 422's are rescued by save on validation, which happens when .create is fired, and are bubbled up as validation errors instead.
Looking at lib/active_resource/validations.rb, you can see the ResourceInvalid exception is gobbled:
# Validate a resource and save (POST) it to the remote web service.
# If any local validations fail - the save (POST) will not be attempted.
def save_with_validation(options={})
perform_validation = options[:validate] != false
# clear the remote validations so they don't interfere with the local
# ones. Otherwise we get an endless loop and can never change the
# fields so as to make the resource valid.
#remote_errors = nil
if perform_validation && valid? || !perform_validation
save_without_validation
true
else
false
end
rescue ResourceInvalid => error
# cache the remote errors because every call to <tt>valid?</tt> clears
# all errors. We must keep a copy to add these back after local
# validations.
#remote_errors = error
load_remote_errors(#remote_errors, true)
false
end
So I wonder if it's logging that an exception happened, but is not actually raising an exception because it turns it in to a return false. It does say "local validations" in the comment, but sets remote_errors, so it's not perfectly clear where this code path is executed.

Is it possible for begin .. rescue to NOT catch an exception?

I have a process that is within a begin rescue loop, that looks like this:
begin
# do some stuff
rescue Exception => e
Rails.logger.info "#{e.response.message}"
end
Is it possible for this to NOT catch an exception? For some reason my process is running, not throwing errors, but randomly not working.
Just use :
# do some stuff
without any begin/rescue block, and see which Error comes out. Let's say it is NoMethodError. Maybe you have a typo in some of your code, like "abc".spilt.
Correct it. Try again, maybe you get Errno::ECONNRESET.
Try :
begin
# do some stuff
rescue Errno::ECONNRESET => e
Rails.logger.info "#{e.message}"
end
Rinse and repeat, but start from scratch. rescue Exception is just too much.
Maybe you can temporary comment rescue block:
#begin
...
#rescue Exception => e
#Rails.logger.info "#{e.response.message}"
or you can raise raise this exception in rescue:
begin
do some stuff
rescue Exception => e
Rails.logger.info "#{e.response.message}"
raise e
end

How do I reschedule a failed Rufus every job?

I have a Rufus "every job" that runs periodically, but sometimes it may fail to perform it's task.
On failure, I would like to reschedule a retry soon rather than wait until the next cycle.
class PollProducts
def initialize()
end
def call(job)
puts "Updating products"
begin
# Do something that might fail
raise if rand(3) == 1
rescue Exception => e
puts "Request failed - recheduling: #{e}"
# job.in("5s") <-- What can I do?
end
end
end
scheduler.every '6h', PollProducts.new, :first_in => '5s', :blocking => true
Is this possible?
Ok this worked for me:
job.scheduler.in '5s', self

How to check for specific rescue clause for error handling in Rails 3.x?

I have the following code:
begin
site = RedirectFollower.new(url).resolve
rescue => e
puts e.to_s
return false
end
Which throws errors like:
the scheme http does not accept registry part: www.officedepot.com;
the scheme http does not accept registry part: ww2.google.com/something;
Operation timed out - connect(2)
How can I add in another rescue for all errors that are like the scheme http does not accept registry part?
Since I want to do something other than just printing the error and returning false in that case.
That depends.
I see the three exception descriptions are different. Are the Exception types different as well?
If So you could write your code like this:
begin
site = RedirectFollower.new(url).resolve
rescue ExceptionType1 => e
#do something with exception that throws 'scheme http does not...'
else
#do something with other exceptions
end
If the exception types are the same then you'll still have a single rescue block but will decide what to do based on a regular expression. Perhaps something like:
begin
site = RedirectFollower.new(url).resolve
rescue Exception => e
if e.message =~ /the scheme http does not accept registry part/
#do something with it
end
end
Does this help?
Check what is exception class in case of 'the scheme http does not accept registry part' ( you can do this by puts e.class). I assume that this will be other than 'Operation timed out - connect(2)'
then:
begin
.
rescue YourExceptionClass => e
.
rescue => e
.
end
Important Note: Rescue with wildcard, defaults to StandardError. It will not rescue every error.
For example, SignalException: SIGTERMwill not be rescued with rescue => error. You will have to specifically use rescue SignalException => e.

Resources