Bug #19598
openInconsistent behaviour of TracePoint API
Description
Hello,
I am seeing inconsistent behaviour of the TracePoint API. If I raise an error from within the :raise
event block it crashes the entire program with a exception reentered (fatal)
next time any error is raised. However if I add a simple if
check in the :raised
event block the same program doesn't crash anymore.
My specific use case is that sometimes when I have Exception
s being raised in my application they are being handled by ActiveRecord and wrapped in a ActiveRecord::StatementInvalid
, which is a StandardError
. The codebase has a lot of rescue StandardError
statements which swallow the StatementInvalid
and therefore the Exception
s get ignored. I would like to bypass the rescue StandardError
statements in this case. My current solution is to manually check in every rescue StandardError
if the StatementInvalid
has an Exception
in its .cause
attribute and if there is re-raise it, but the codebase is very big and this is not a very good solution as every developer needs to remember to do this check if they add a new rescue StandardError
or modify an existing one.
Using TracePoint to do the aforementioned check before any rescue
statements are called and then re-raise the Exception seems like a very neat way to automate the handling of these masked Exception
s. However I am getting inconsistent behaviour from Ruby depending on what code I put inside the :raised
event handler. Here are two identical pieces of code apart from an extra if
check in the second example. The first example crashes with exception reentered (fatal)
, the second doesn't.
Code to reproduce crash¶
require "active_record"
class Test
def run
begin
tp = TracePoint.new(:raise) do |t|
puts "TracePoint received: #{t.raised_exception.class}"
raise t.raised_exception.cause
end
puts "TracePoint created"
tp.enable do
puts "TracePoint enabled"
# Generate an Exception masked as a StatementInvalid
begin
raise Exception
catch Exception
raise ActiveRecord::StatementInvalid
end
end
rescue Exception => e
puts "Got Exception instead of StatementInvalid"
end
end
end
t = Test.new
t.run
begin
raise ArgumentError
rescue ArgumentError => e
puts "Never reach here"
end
Output¶
TracePoint created
TracePoint enabled
TracePoint received: Exception
Got Exception instead of StatementInvalid
tp_test2.rb: exception reentered (fatal)
Code that doesn't crash, extra if check on line 8¶
require "active_record"
class Test
def run
begin
tp = TracePoint.new(:raise) do |t|
puts "TracePoint received: #{t.raised_exception.class}"
if t.raised_exception.instance_of?(ActiveRecord::StatementInvalid)
raise t.raised_exception.cause
end
end
puts "TracePoint created"
tp.enable do
puts "TracePoint enabled"
# Generate an Exception masked as a StatementInvalid
begin
raise Exception
catch Exception
raise ActiveRecord::StatementInvalid
end
end
rescue Exception => e
puts "Got Exception instead of StatementInvalid"
end
end
end
t = Test.new
t.run
begin
raise ArgumentError
rescue ArgumentError => e
puts "Never reach here"
end
Output¶
TracePoint created
TracePoint enabled
TracePoint received: Exception
Got Exception instead of StatementInvalid
Never reach here