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

Make legacy_connection_handling a module instance variable

Ref: https://github.com/rails/rails/pull/42237
Ref: https://bugs.ruby-lang.org/issues/17763

Ruby class variables are slow, and get slower the more ancestors the class has.
And it doesn't seem likely to get faster any time soon. Overall I'm under the
impression that ruby-core consider them more or less deprecated.

But in many cases where `cattr_accessor` is used, a class instance variable
could work just fine, and would be much faster.

For Active Record this means most of the `cattr_accessor` on `ActiveRecord::Base`
could actually be `attr_accessor` on `ActiveRecord`.

I started with `legacy_connection_handling` as a proof of concept.
This commit is contained in:
Jean Boussier 2021-06-10 13:00:18 +02:00
parent 4a85239c3e
commit 8459c1c22f
20 changed files with 82 additions and 70 deletions

View file

@ -169,6 +169,9 @@ module ActiveRecord
autoload :TestDatabases, "active_record/test_databases"
autoload :TestFixtures, "active_record/fixtures"
singleton_class.attr_accessor :legacy_connection_handling
self.legacy_connection_handling = true
def self.eager_load!
super
ActiveRecord::Locking.eager_load!

View file

@ -100,7 +100,7 @@ module ActiveRecord
# See +READ_QUERY+ for the queries that are blocked by this
# method.
def while_preventing_writes(enabled = true)
unless ActiveRecord::Base.legacy_connection_handling
unless ActiveRecord.legacy_connection_handling
raise NotImplementedError, "`while_preventing_writes` is only available on the connection_handler with legacy_connection_handling"
end
@ -143,7 +143,7 @@ module ActiveRecord
payload[:config] = db_config.configuration_hash
end
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
owner_to_pool_manager[pool_config.connection_specification_name] ||= LegacyPoolManager.new
else
owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new

View file

@ -140,7 +140,7 @@ module ActiveRecord
# will return true based on +current_preventing_writes+.
def preventing_writes?
return true if replica?
return ActiveRecord::Base.connection_handler.prevent_writes if ActiveRecord::Base.legacy_connection_handling
return ActiveRecord::Base.connection_handler.prevent_writes if ActiveRecord.legacy_connection_handling
return false if connection_klass.nil?
connection_klass.current_preventing_writes

View file

@ -135,7 +135,7 @@ module ActiveRecord
# Dog.first # finds first Dog record stored on the shard one replica
# end
def connected_to(role: nil, shard: nil, prevent_writes: false, &blk)
if legacy_connection_handling
if ActiveRecord.legacy_connection_handling
if self != Base
raise NotImplementedError, "`connected_to` can only be called on ActiveRecord::Base with legacy connection handling."
end
@ -176,7 +176,7 @@ module ActiveRecord
def connected_to_many(*classes, role:, shard: nil, prevent_writes: false)
classes = classes.flatten
if legacy_connection_handling
if ActiveRecord.legacy_connection_handling
raise NotImplementedError, "connected_to_many is not available with legacy connection handling"
end
@ -200,7 +200,7 @@ module ActiveRecord
# It is not recommended to use this method in a request since it
# does not yield to a block like +connected_to+.
def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
if legacy_connection_handling
if ActiveRecord.legacy_connection_handling
raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`."
end
@ -221,7 +221,7 @@ module ActiveRecord
# See +READ_QUERY+ for the queries that are blocked by this
# method.
def while_preventing_writes(enabled = true, &block)
if legacy_connection_handling
if ActiveRecord.legacy_connection_handling
connection_handler.while_preventing_writes(enabled, &block)
else
connected_to(role: current_role, prevent_writes: enabled, &block)
@ -239,7 +239,7 @@ module ActiveRecord
end
def lookup_connection_handler(handler_key) # :nodoc:
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
handler_key ||= ActiveRecord::Base.writing_role
connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
else
@ -249,7 +249,7 @@ module ActiveRecord
# Clears the query cache for all connections associated with the current thread.
def clear_query_caches_for_current_thread
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
ActiveRecord::Base.connection_handlers.each_value do |handler|
clear_on_handler(handler)
end
@ -358,7 +358,7 @@ module ActiveRecord
def with_role_and_shard(role, shard, prevent_writes)
prevent_writes = true if role == reading_role
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
with_handler(role.to_sym) do
connection_handler.while_preventing_writes(prevent_writes) do
self.connected_to_stack << { shard: shard, klasses: [self] }

View file

@ -164,8 +164,6 @@ module ActiveRecord
class_attribute :default_shard, instance_writer: false
mattr_accessor :legacy_connection_handling, instance_writer: false, default: true
mattr_accessor :application_record_class, instance_accessor: false, default: nil
# Sets the async_query_executor for an application. By default the thread pool executor
@ -225,7 +223,7 @@ module ActiveRecord
end
def self.connection_handlers
if legacy_connection_handling
if ActiveRecord.legacy_connection_handling
else
raise NotImplementedError, "The new connection handling does not support accessing multiple connection handlers."
end
@ -234,7 +232,7 @@ module ActiveRecord
end
def self.connection_handlers=(handlers)
if legacy_connection_handling
if ActiveRecord.legacy_connection_handling
ActiveSupport::Deprecation.warn(<<~MSG)
Using legacy connection handling is deprecated. Please set
`legacy_connection_handling` to `false` in your application.
@ -270,7 +268,7 @@ module ActiveRecord
# ActiveRecord::Base.current_role #=> :reading
# end
def self.current_role
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
connection_handlers.key(connection_handler) || default_role
else
connected_to_stack.reverse_each do |hash|
@ -311,7 +309,7 @@ module ActiveRecord
# ActiveRecord::Base.current_preventing_writes #=> false
# end
def self.current_preventing_writes
if legacy_connection_handling
if ActiveRecord.legacy_connection_handling
connection_handler.prevent_writes
else
connected_to_stack.reverse_each do |hash|

View file

@ -28,7 +28,7 @@ module ActiveRecord
def self.run
pools = []
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
ActiveRecord::Base.connection_handlers.each do |key, handler|
pools.concat(handler.connection_pool_list.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! })
end
@ -42,7 +42,7 @@ module ActiveRecord
def self.complete(pools)
pools.each { |pool| pool.disable_query_cache! }
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
ActiveRecord::Base.connection_handlers.each do |_, handler|
handler.connection_pool_list.each do |pool|
pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?

View file

@ -200,11 +200,22 @@ To keep using the current cache store, you can turn off cache versioning entirel
end
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
configs = app.config.active_record
configs = app.config.active_record
configs.each do |k, v|
next if k == :encryption
setter = "#{k}="
if ActiveRecord.respond_to?(setter)
ActiveRecord.send(setter, v)
end
end
ActiveSupport.on_load(:active_record) do
configs.each do |k, v|
send "#{k}=", v if k != :encryption
next if k == :encryption
setter = "#{k}="
next if ActiveRecord.respond_to?(setter)
send(setter, v)
end
end
end
@ -213,7 +224,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
# and then establishes the connection.
initializer "active_record.initialize_database" do
ActiveSupport.on_load(:active_record) do
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
self.connection_handlers = { writing_role => ActiveRecord::Base.default_connection_handler }
end
self.configurations = Rails.application.config.database_configuration

View file

@ -193,7 +193,7 @@ module ActiveRecord
# need to share a connection pool so that the reading connection
# can see data in the open transaction on the writing connection.
def setup_shared_connection_pool
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
writing_handler = ActiveRecord::Base.connection_handlers[ActiveRecord::Base.writing_role]
ActiveRecord::Base.connection_handlers.values.each do |handler|
@ -236,7 +236,7 @@ module ActiveRecord
end
def teardown_shared_connection_pool
if ActiveRecord::Base.legacy_connection_handling
if ActiveRecord.legacy_connection_handling
@legacy_saved_pool_configs.each_pair do |handler, names|
names.each_pair do |name, shards|
shards.each_pair do |shard_name, pool_config|

View file

@ -127,8 +127,8 @@ module ActiveRecord
class AdapterPreventWritesLegacyTest < ActiveRecord::TestCase
def setup
@old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
@old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
@connection = ActiveRecord::Base.connection
@connection_handler = ActiveRecord::Base.connection_handler
@ -136,7 +136,7 @@ module ActiveRecord
def teardown
clean_up_legacy_connection_handlers
ActiveRecord::Base.legacy_connection_handling = @old_value
ActiveRecord.legacy_connection_handling = @old_value
end
def test_preventing_writes_predicate_legacy

View file

@ -105,15 +105,15 @@ class Mysql2AdapterPreventWritesLegacyTest < ActiveRecord::Mysql2TestCase
include DdlHelper
def setup
@old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
@old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
@conn = ActiveRecord::Base.connection
@connection_handler = ActiveRecord::Base.connection_handler
end
def teardown
ActiveRecord::Base.legacy_connection_handling = @old_value
ActiveRecord.legacy_connection_handling = @old_value
end
def test_errors_when_an_insert_query_is_called_while_preventing_writes

View file

@ -106,15 +106,15 @@ module ActiveRecord
include ConnectionHelper
def setup
@old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
@old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
@connection = ActiveRecord::Base.connection
@connection_handler = ActiveRecord::Base.connection_handler
end
def teardown
ActiveRecord::Base.legacy_connection_handling = @old_value
ActiveRecord.legacy_connection_handling = @old_value
end
def test_errors_when_an_insert_query_is_called_while_preventing_writes

View file

@ -95,8 +95,8 @@ module ActiveRecord
self.use_transactional_tests = false
def setup
@old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
@old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
@conn = ActiveRecord::Base.connection
@ -104,7 +104,7 @@ module ActiveRecord
end
def teardown
ActiveRecord::Base.legacy_connection_handling = @old_value
ActiveRecord.legacy_connection_handling = @old_value
end
def test_errors_when_an_insert_query_is_called_while_preventing_writes

View file

@ -97,15 +97,15 @@ class BasePreventWritesTest < ActiveRecord::TestCase
class BasePreventWritesLegacyTest < ActiveRecord::TestCase
def setup
@old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
@old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
ActiveRecord::Base.establish_connection :arunit
ARUnit2Model.establish_connection :arunit2
end
def teardown
clean_up_legacy_connection_handlers
ActiveRecord::Base.legacy_connection_handling = @old_value
ActiveRecord.legacy_connection_handling = @old_value
end
if !in_memory_db?

View file

@ -1682,8 +1682,8 @@ class BasicsTest < ActiveRecord::TestCase
end
test "cannot call connected_to on subclasses of ActiveRecord::Base with legacy connection handling" do
old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
error = assert_raises(NotImplementedError) do
Bird.connected_to(role: :reading) { }
@ -1692,7 +1692,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "`connected_to` can only be called on ActiveRecord::Base with legacy connection handling.", error.message
ensure
clean_up_legacy_connection_handlers
ActiveRecord::Base.legacy_connection_handling = old_value
ActiveRecord.legacy_connection_handling = old_value
end
test "cannot call connected_to with role and shard on non-abstract classes" do
@ -1744,25 +1744,25 @@ class BasicsTest < ActiveRecord::TestCase
end
test "#connecting_to doesn't work with legacy connection handling" do
old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
assert_raises NotImplementedError do
SecondAbstractClass.connecting_to(role: :writing, prevent_writes: true)
end
ensure
ActiveRecord::Base.legacy_connection_handling = old_value
ActiveRecord.legacy_connection_handling = old_value
end
test "#connected_to_many doesn't work with legacy connection handling" do
old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
assert_raises NotImplementedError do
ActiveRecord::Base.connected_to_many([SecondAbstractClass], role: :writing)
end
ensure
ActiveRecord::Base.legacy_connection_handling = old_value
ActiveRecord.legacy_connection_handling = old_value
end
test "#connected_to_many cannot be called on anything but ActiveRecord::Base" do

View file

@ -11,8 +11,8 @@ module ActiveRecord
fixtures :people
def setup
@old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
@old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
assert_deprecated do
ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
end
@ -29,7 +29,7 @@ module ActiveRecord
def teardown
clean_up_legacy_connection_handlers
ActiveRecord::Base.legacy_connection_handling = @old_value
ActiveRecord.legacy_connection_handling = @old_value
end
class SecondaryBase < ActiveRecord::Base

View file

@ -11,8 +11,8 @@ module ActiveRecord
fixtures :people
def setup
@legacy_setting = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
@legacy_setting = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
assert_deprecated do
ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
end
@ -29,7 +29,7 @@ module ActiveRecord
def teardown
clean_up_legacy_connection_handlers
ActiveRecord::Base.legacy_connection_handling = @legacy_setting
ActiveRecord.legacy_connection_handling = @legacy_setting
end
unless in_memory_db?

View file

@ -1541,8 +1541,8 @@ if current_adapter?(:SQLite3Adapter) && !in_memory_db?
fixtures :dogs
def setup
@old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
@old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
@old_handler = ActiveRecord::Base.connection_handler
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
@ -1565,7 +1565,7 @@ if current_adapter?(:SQLite3Adapter) && !in_memory_db?
ActiveRecord::Base.configurations = @prev_configs
ActiveRecord::Base.connection_handler = @old_handler
clean_up_legacy_connection_handlers
ActiveRecord::Base.legacy_connection_handling = false
ActiveRecord.legacy_connection_handling = false
end
def test_uses_writing_connection_for_fixtures

View file

@ -75,8 +75,8 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_query_cache_is_applied_to_legacy_connections_in_all_handlers
old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
assert_deprecated do
ActiveRecord::Base.connection_handlers = {
@ -101,7 +101,7 @@ class QueryCacheTest < ActiveRecord::TestCase
mw.call({})
ensure
clean_up_legacy_connection_handlers
ActiveRecord::Base.legacy_connection_handling = old_value
ActiveRecord.legacy_connection_handling = old_value
end
def test_query_cache_is_applied_to_all_connections
@ -126,8 +126,8 @@ class QueryCacheTest < ActiveRecord::TestCase
if Process.respond_to?(:fork) && !in_memory_db?
def test_query_cache_with_multiple_handlers_and_forked_processes_legacy_handling
old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
assert_deprecated do
ActiveRecord::Base.connection_handlers = {
@ -190,7 +190,7 @@ class QueryCacheTest < ActiveRecord::TestCase
rd.close
ensure
clean_up_legacy_connection_handlers
ActiveRecord::Base.legacy_connection_handling = old_value
ActiveRecord.legacy_connection_handling = old_value
end
def test_query_cache_with_forked_processes
@ -665,8 +665,8 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_clear_query_cache_is_called_on_all_legacy_connections
skip "with in memory db, reading role won't be able to see database on writing role" if in_memory_db?
old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
assert_deprecated do
ActiveRecord::Base.connection_handlers = {
@ -704,7 +704,7 @@ class QueryCacheTest < ActiveRecord::TestCase
ensure
unless in_memory_db?
clean_up_legacy_connection_handlers
ActiveRecord::Base.legacy_connection_handling = old_value
ActiveRecord.legacy_connection_handling = old_value
end
end

View file

@ -23,8 +23,8 @@ class TestFixturesTest < ActiveRecord::TestCase
unless in_memory_db?
def test_doesnt_rely_on_active_support_test_case_specific_methods_with_legacy_connection_handling
old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
old_value = ActiveRecord.legacy_connection_handling
ActiveRecord.legacy_connection_handling = true
tmp_dir = Dir.mktmpdir
File.write(File.join(tmp_dir, "zines.yml"), <<~YML)
@ -59,7 +59,7 @@ class TestFixturesTest < ActiveRecord::TestCase
clean_up_legacy_connection_handlers
ActiveRecord::Base.connection_handler = old_handler
FileUtils.rm_r(tmp_dir)
ActiveRecord::Base.legacy_connection_handling = old_value
ActiveRecord.legacy_connection_handling = old_value
end
def test_doesnt_rely_on_active_support_test_case_specific_methods

View file

@ -19,7 +19,7 @@ module ARTest
end
def self.connect
ActiveRecord::Base.legacy_connection_handling = false
ActiveRecord.legacy_connection_handling = false
ActiveRecord::Base.async_query_executor = :global_thread_pool
puts "Using #{connection_name}"
ActiveRecord::Base.logger = ActiveSupport::Logger.new("debug.log", 0, 100 * 1024 * 1024)