[ruby-core:113222] [Ruby master Bug#19598] Inconsistent behaviour of TracePoint API

Issue #19598 has been reported by bgdimitrov (Bogdan Dimitrov). ---------------------------------------- Bug #19598: Inconsistent behaviour of TracePoint API https://bugs.ruby-lang.org/issues/19598 * Author: bgdimitrov (Bogdan Dimitrov) * Status: Open * Priority: Normal * ruby -v: ruby 3.1.4p223 (2023-03-30 revision 957bb7cb81) [x86_64-darwin22] * Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN ---------------------------------------- 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 ``` -- https://bugs.ruby-lang.org/

Issue #19598 has been updated by ko1 (Koichi Sasada). (1) More simple reproducible code is very welcome because it is easy to understand the situation. (2) `catch Exception` => `rescue Exception` ---- off topic: I wonder it is valid code: ``` begin raise 'foo' catch => e p e end ``` because it is parsed as ``` begin raise 'foo' catch() => e # 1 line pattern match but not reached because of `raise` p e end ``` ---------------------------------------- Bug #19598: Inconsistent behaviour of TracePoint API https://bugs.ruby-lang.org/issues/19598#change-102773 * Author: bgdimitrov (Bogdan Dimitrov) * Status: Open * Priority: Normal * ruby -v: ruby 3.1.4p223 (2023-03-30 revision 957bb7cb81) [x86_64-darwin22] * Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN ---------------------------------------- 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 ``` -- https://bugs.ruby-lang.org/

Issue #19598 has been updated by alanwu (Alan Wu). Side note, `Kernel#catch` without a block doesn't seem to make sense. Maybe it should raise "no block given" like `Kernel#tap`. ---------------------------------------- Bug #19598: Inconsistent behaviour of TracePoint API https://bugs.ruby-lang.org/issues/19598#change-102775 * Author: bgdimitrov (Bogdan Dimitrov) * Status: Open * Priority: Normal * ruby -v: ruby 3.1.4p223 (2023-03-30 revision 957bb7cb81) [x86_64-darwin22] * Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN ---------------------------------------- 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 ``` -- https://bugs.ruby-lang.org/

Issue #19598 has been updated by bgdimitrov (Bogdan Dimitrov). Thank you, the `catch` instead of `rescue` was causing the inconsistency, changing that makes both examples fail with `exception reentered (fatal)`. Is this behaviour expected though? It looks like after raising an error from within the TracePoint the whole error-raising mechanism is broken, other code runs ok (e.g. the `puts` statement on line 24) but when we try to `raise` again we get the `exception reentered (fatal)`. I have slimmed down the example to this: ```ruby $tp = TracePoint.new(:raise) do |t| puts "TracePoint received #{t.raised_exception.class}" puts "TracePoint raising ArgumentError" raise ArgumentError end class Test def run $tp.enable do begin puts "Raising NameError" raise NameError rescue ArgumentError puts "Handled TracePoint's ArgumentError" end end end end t = Test.new t.run begin puts "Raising follow-up error" raise NotImplementedError rescue NotImplementedError => e puts "Never reach here" end ``` Output: ``` Raising NameError TracePoint received NameError TracePoint raising ArgumentError Handled TracePoint's ArgumentError Raising follow-up error tp_test3.rb: exception reentered (fatal) ``` ---------------------------------------- Bug #19598: Inconsistent behaviour of TracePoint API https://bugs.ruby-lang.org/issues/19598#change-102804 * Author: bgdimitrov (Bogdan Dimitrov) * Status: Open * Priority: Normal * ruby -v: ruby 3.1.4p223 (2023-03-30 revision 957bb7cb81) [x86_64-darwin22] * Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN ---------------------------------------- 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 ``` -- https://bugs.ruby-lang.org/
participants (3)
-
alanwu (Alan Wu)
-
bgdimitrov (Bogdan Dimitrov)
-
ko1 (Koichi Sasada)