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 current_version = begin
ActiveRecord::Migrator.current_version ActiveRecord::Migrator.current_version
rescue ActiveRecordError => error 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 nil
end end
next if current_version.nil? next if current_version.nil?
@ -167,18 +167,31 @@ To keep using the current cache store, you can turn off cache versioning entirel
config.after_initialize do config.after_initialize do
ActiveSupport.on_load(:active_record) do ActiveSupport.on_load(:active_record) do
if app.config.eager_load if app.config.eager_load
begin
descendants.each do |model| descendants.each do |model|
# SchemaMigration and InternalMetadata both override `table_exists?` # SchemaMigration and InternalMetadata both override `table_exists?`
# to bypass the schema cache, so skip them to avoid the extra queries. # to bypass the schema cache, so skip them to avoid the extra queries.
next if model._internal? next if model._internal?
# If there's no connection yet, or the schema cache doesn't have the columns # If the schema cache was loaded from a dump, we can use it without connecting
# hash for the model cached, `define_attribute_methods` would trigger a query. schema_cache = model.connection_pool.schema_cache
next unless model.connected? && model.connection.schema_cache.columns_hash?(model.table_name)
# 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 model.define_attribute_methods
end end
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 end
end end
end end

View file

@ -1,10 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
require "isolation/abstract_unit" require "isolation/abstract_unit"
require "env_helpers"
module ApplicationTests module ApplicationTests
class FrameworksTest < ActiveSupport::TestCase class FrameworksTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation include ActiveSupport::Testing::Isolation
include EnvHelpers
def setup def setup
build_app build_app
@ -214,10 +216,24 @@ module ApplicationTests
assert !defined?(ActiveRecord::Base) || ActiveRecord.autoload?(:Base) assert !defined?(ActiveRecord::Base) || ActiveRecord.autoload?(:Base)
end 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 test "use schema cache dump" do
rails %w(generate model post title:string) rails %w(generate model post title:string)
rails %w(db:migrate db:schema:cache:dump) rails %w(db:migrate db:schema:cache:dump)
add_to_config <<-RUBY
config.eager_load = true
RUBY
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
assert ActiveRecord::Base.connection.schema_cache.data_sources("posts") assert ActiveRecord::Base.connection.schema_cache.data_sources("posts")
ensure ensure
ActiveRecord::Base.connection.drop_table("posts", if_exists: true) # force drop posts table for test. 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 test "expire schema cache dump" do
rails %w(generate model post title:string) rails %w(generate model post title:string)
rails %w(db:migrate db:schema:cache:dump db:rollback) rails %w(db:migrate db:schema:cache:dump db:rollback)
add_to_config <<-RUBY
config.eager_load = true
RUBY
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
assert_not ActiveRecord::Base.connection.schema_cache.data_sources("posts") assert_not ActiveRecord::Base.connection.schema_cache.data_sources("posts")
end 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 add_to_config <<-RUBY
config.active_record.check_schema_cache_dump_version = false config.eager_load = true
RUBY 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(generate model post title:string)
rails %w(db:migrate db:schema:cache:dump db:rollback) 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" require "#{app_path}/config/environment"
assert ActiveRecord::Base.connection_pool.schema_cache.data_sources("posts") assert ActiveRecord::Base.connection_pool.schema_cache.data_sources("posts")
end 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 test "active record establish_connection uses Rails.env if DATABASE_URL is not set" do
require "#{app_path}/config/environment" require "#{app_path}/config/environment"
orig_database_url = ENV.delete("DATABASE_URL") orig_database_url = ENV.delete("DATABASE_URL")