Project

General

Profile

Bug #19598

Updated by alanwu (Alan Wu) about 1 year ago

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 
 ```ruby ``` 
 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 
 ```ruby ``` 
 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 
 ``` 

Back