Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid rescuing from errors in Active Record transaction callbacks in versions of Rails where they will be re-raised #717

Merged
merged 5 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

## TBD

### Fixes

* Avoid rescuing from errors in Active Record transaction callbacks in versions of Rails where they will be re-raised
| [#709](https://github.com/bugsnag/bugsnag-ruby/pull/709)
| [apalmblad](https://github.com/apalmblad)

## v6.24.1 (30 November 2021)

### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,21 @@ def index
def error
generate_unhandled_error
end

def error_in_active_record_callback
User.class_eval do
after_commit { raise 'Oh no!' }
end

user = User.new({
email: "uuu3@example.com",
name: "User U. User III",
first_name: "User",
last_name: "User"
})

user.save

render json: {}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,22 @@ def index
def error
generate_unhandled_error
end

def error_in_active_record_callback
User.class_eval do
after_commit { raise 'Oh no!' }
end

user = User.new({
email: "uuu3@example.com",
password: "*******",
name: "User U. User III",
first_name: "User",
last_name: "User"
})

user.save

render json: {}
end
end
6 changes: 6 additions & 0 deletions features/fixtures/rails4/app/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@ class Application < Rails::Application
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de

raise_in_transactional_callbacks = ActiveRecord::Type::Boolean.new.type_cast_from_user(
ENV.fetch("RAISE_IN_TRANSACTIONAL_CALLBACKS", "true")
)

config.active_record.raise_in_transactional_callbacks = raise_in_transactional_callbacks
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,22 @@ class UnhandledController < ActionController::Base
def error
generate_unhandled_error
end

def error_in_active_record_callback
User.class_eval do
after_commit { raise 'Oh no!' }
end

user = User.new({
email: "uuu3@example.com",
password: "*******",
name: "User U. User III",
first_name: "User",
last_name: "User"
})

user.save

render json: {}
end
end
1 change: 1 addition & 0 deletions features/fixtures/rails5/app/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
get '/', to: 'application#index'

get 'unhandled/error', to: 'unhandled#error'
get 'unhandled/error_in_active_record_callback', to: 'unhandled#error_in_active_record_callback'

get 'handled/unthrown', to: 'handled#unthrown'
get 'handled/thrown', to: 'handled#thrown'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,22 @@ class UnhandledController < ActionController::Base
def error
generate_unhandled_error
end

def error_in_active_record_callback
User.class_eval do
after_commit { raise 'Oh no!' }
end

user = User.new({
email: "uuu3@example.com",
password: "*******",
name: "User U. User III",
first_name: "User",
last_name: "User"
})

user.save

render json: {}
end
end
1 change: 1 addition & 0 deletions features/fixtures/rails6/app/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
get '/', to: 'application#index'

get 'unhandled/error', to: 'unhandled#error'
get 'unhandled/error_in_active_record_callback', to: 'unhandled#error_in_active_record_callback'

get 'handled/unthrown', to: 'handled#unthrown'
get 'handled/thrown', to: 'handled#thrown'
Expand Down
29 changes: 29 additions & 0 deletions features/rails_features/active_record.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Feature: Active Record

@rails3 @rails4 @rails5 @rails6
Scenario: An unhandled error in a transaction callback will be delivered
Given I start the rails service
When I navigate to the route "/unhandled/error_in_active_record_callback" on the rails app
And I wait to receive a request
Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier"
And the event "unhandled" is true
And the exception "errorClass" equals "RuntimeError"
And the exception "message" equals "Oh no!"
And the event "app.type" equals "rails"
And the event "metaData.request.url" ends with "/unhandled/error_in_active_record_callback"
And the event "severity" equals "error"

# The "raise_in_transactional_callbacks" config option only exists in Rails 4.2
@rails4
Scenario: An unhandled error in a transaction callback will be delivered when raise in transactional callbacks is false
Given I set environment variable "RAISE_IN_TRANSACTIONAL_CALLBACKS" to "false"
And I start the rails service
When I navigate to the route "/unhandled/error_in_active_record_callback" on the rails app
And I wait to receive a request
Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier"
And the event "unhandled" is true
And the exception "errorClass" equals "RuntimeError"
And the exception "message" equals "Oh no!"
And the event "app.type" equals "rails"
And the event "metaData.request.url" ends with "/unhandled/error_in_active_record_callback"
And the event "severity" equals "error"
29 changes: 27 additions & 2 deletions lib/bugsnag/integrations/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,29 @@ def event_subscription(event)
end
end

##
# Do we need to rescue (& notify) in Active Record callbacks?
#
# On Rails versions < 4.2, Rails did not raise errors in AR callbacks
# On Rails version 4.2, a config option was added to control this
# On Rails version 5.0, the config option was removed and errors in callbacks
# always bubble up
#
# @api private
def self.rescue_in_active_record_callbacks?
# Rails 5+ will re-raise errors in callbacks, so we don't need to rescue them
return false if ::Rails::VERSION::MAJOR > 4

# before 4.2, errors were always swallowed, so we need to rescue them
return true if ::Rails::VERSION::MAJOR < 4

# a config option was added in 4.2 to control this, but won't exist in 4.0 & 4.1
return true unless ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks)

# if the config option is false, we need to rescue and notify
ActiveRecord::Base.raise_in_transactional_callbacks == false
end

rake_tasks do
require "bugsnag/integrations/rake"
load "bugsnag/tasks/bugsnag.rake"
Expand All @@ -70,8 +93,10 @@ def event_subscription(event)
end

ActiveSupport.on_load(:active_record) do
require "bugsnag/integrations/rails/active_record_rescue"
include Bugsnag::Rails::ActiveRecordRescue
if Bugsnag::Railtie.rescue_in_active_record_callbacks?
require "bugsnag/integrations/rails/active_record_rescue"
include Bugsnag::Rails::ActiveRecordRescue
end
end

ActiveSupport.on_load(:active_job) do
Expand Down