1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Rescue Database errors during active_record.define_attribute_methods

This initializer should be best effort. If for some reason the
database in unhealthy, it is better to move forward with the
boot process than to crash.
This commit is contained in:
Jean Boussier 2020-08-27 11:38:14 +02:00
parent 7101489293
commit 36fae4bdaf
2 changed files with 85 additions and 11 deletions

View file

@ -146,7 +146,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
current_version = begin
ActiveRecord::Migrator.current_version
rescue ActiveRecordError => error
warn "Failed to load the schema cache because of #{error.class}: #{error.message}"
warn "Failed to validate the schema cache because of #{error.class}: #{error.message}"
nil
end
next if current_version.nil?
@ -167,16 +167,29 @@ To keep using the current cache store, you can turn off cache versioning entirel
config.after_initialize do
ActiveSupport.on_load(:active_record) do
if app.config.eager_load
descendants.each do |model|
# SchemaMigration and InternalMetadata both override `table_exists?`
# to bypass the schema cache, so skip them to avoid the extra queries.
next if model._internal?
begin
descendants.each do |model|
# SchemaMigration and InternalMetadata both override `table_exists?`
# to bypass the schema cache, so skip them to avoid the extra queries.
next if model._internal?
# If there's no connection yet, or the schema cache doesn't have the columns
# hash for the model cached, `define_attribute_methods` would trigger a query.
next unless model.connected? && model.connection.schema_cache.columns_hash?(model.table_name)
# If the schema cache was loaded from a dump, we can use it without connecting
schema_cache = model.connection_pool.schema_cache
model.define_attribute_methods
# If there's no connection yet, we avoid connecting.
schema_cache ||= model.connected? && model.connection.schema_cache
# If the schema cache doesn't have the columns
# hash for the model cached, `define_attribute_methods` would trigger a query.
if schema_cache && schema_cache.columns_hash?(model.table_name)
model.define_attribute_methods
end
end
rescue ActiveRecordError => error
# Regardless of wether there was already a connection or not, we rescue any database
# error because it is critical that the application can boot even if the database
# is unhealthy.
warn "Failed to define attribute methods because of #{error.class}: #{error.message}"
end
end
end

View file

@ -1,10 +1,12 @@
# frozen_string_literal: true
require "isolation/abstract_unit"
require "env_helpers"
module ApplicationTests
class FrameworksTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
include EnvHelpers
def setup
build_app
@ -214,10 +216,24 @@ module ApplicationTests
assert !defined?(ActiveRecord::Base) || ActiveRecord.autoload?(:Base)
end
test "can boot with an unhealthy database" do
rails %w(generate model post title:string)
switch_env("DATABASE_URL", "mysql2://127.0.0.1:1") do
require "#{app_path}/config/environment"
end
end
test "use schema cache dump" do
rails %w(generate model post title:string)
rails %w(db:migrate db:schema:cache:dump)
add_to_config <<-RUBY
config.eager_load = true
RUBY
require "#{app_path}/config/environment"
assert ActiveRecord::Base.connection.schema_cache.data_sources("posts")
ensure
ActiveRecord::Base.connection.drop_table("posts", if_exists: true) # force drop posts table for test.
@ -226,20 +242,65 @@ module ApplicationTests
test "expire schema cache dump" do
rails %w(generate model post title:string)
rails %w(db:migrate db:schema:cache:dump db:rollback)
add_to_config <<-RUBY
config.eager_load = true
RUBY
require "#{app_path}/config/environment"
assert_not ActiveRecord::Base.connection.schema_cache.data_sources("posts")
end
test "does not expire schema cache dump if check_schema_cache_dump_version is false" do
test "expire schema cache dump if the version can't be checked because the database is unhealthy" do
rails %w(generate model post title:string)
rails %w(db:migrate db:schema:cache:dump)
add_to_config <<-RUBY
config.active_record.check_schema_cache_dump_version = false
config.eager_load = true
RUBY
switch_env("DATABASE_URL", "mysql2://127.0.0.1:1") do
require "#{app_path}/config/environment"
assert_nil ActiveRecord::Base.connection_pool.schema_cache
assert_raises ActiveRecord::ConnectionNotEstablished do
ActiveRecord::Base.connection.execute("SELECT 1")
end
end
end
test "does not expire schema cache dump if check_schema_cache_dump_version is false" do
rails %w(generate model post title:string)
rails %w(db:migrate db:schema:cache:dump db:rollback)
add_to_config <<-RUBY
config.eager_load = true
config.active_record.check_schema_cache_dump_version = false
RUBY
require "#{app_path}/config/environment"
assert ActiveRecord::Base.connection_pool.schema_cache.data_sources("posts")
end
test "does not expire schema cache dump if check_schema_cache_dump_version is false and the database unhealthy" do
rails %w(generate model post title:string)
rails %w(db:migrate db:schema:cache:dump db:rollback)
add_to_config <<-RUBY
config.eager_load = true
config.active_record.check_schema_cache_dump_version = false
RUBY
switch_env("DATABASE_URL", "mysql2://127.0.0.1:1") do
require "#{app_path}/config/environment"
assert ActiveRecord::Base.connection_pool.schema_cache.data_sources("posts")
assert_raises ActiveRecord::ConnectionNotEstablished do
ActiveRecord::Base.connection.execute("SELECT 1")
end
end
end
test "active record establish_connection uses Rails.env if DATABASE_URL is not set" do
require "#{app_path}/config/environment"
orig_database_url = ENV.delete("DATABASE_URL")