Bug #19598
Updated by alanwu (Alan Wu) over 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 ```