Skip to content

Commit b8b00ef

Browse files
authored
Merge pull request rails#54366 from flavorjones/flavorjones-dont-cache-class-in-pool-config-v3
`PoolConfig` no longer keeps a reference to the connection class (attempt 3)
2 parents 4fe4125 + 2a90dbc commit b8b00ef

File tree

8 files changed

+79
-30
lines changed

8 files changed

+79
-30
lines changed

activerecord/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
* PoolConfig no longer keeps a reference to the connection class.
2+
3+
Keeping a reference to the class caused subtle issues when combined with reloading in
4+
development. Fixes #54343.
5+
6+
*Mike Dalessio*
7+
18
* Fix SQL notifications sometimes not sent when using async queries.
29

310
```ruby

activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,22 @@ module ConnectionAdapters
5454
# about the model. The model needs to pass a connection specification name to the handler,
5555
# in order to look up the correct connection pool.
5656
class ConnectionHandler
57-
class StringConnectionName # :nodoc:
58-
attr_reader :name
59-
60-
def initialize(name)
57+
class ConnectionDescriptor # :nodoc:
58+
def initialize(name, primary = false)
6159
@name = name
60+
@primary = primary
61+
end
62+
63+
def name
64+
primary_class? ? "ActiveRecord::Base" : @name
6265
end
6366

6467
def primary_class?
65-
false
68+
@primary
6669
end
6770

6871
def current_preventing_writes
69-
false
72+
ActiveRecord::Base.preventing_writes?(@name)
7073
end
7174
end
7275

@@ -115,7 +118,7 @@ def establish_connection(config, owner_name: Base, role: Base.current_role, shar
115118
pool_config = resolve_pool_config(config, owner_name, role, shard)
116119
db_config = pool_config.db_config
117120

118-
pool_manager = set_pool_manager(pool_config.connection_name)
121+
pool_manager = set_pool_manager(pool_config.connection_descriptor)
119122

120123
# If there is an existing pool with the same values as the pool_config
121124
# don't remove the connection. Connections should only be removed if we are
@@ -127,8 +130,8 @@ def establish_connection(config, owner_name: Base, role: Base.current_role, shar
127130
# Update the pool_config's connection class if it differs. This is used
128131
# for ensuring that ActiveRecord::Base and the primary_abstract_class use
129132
# the same pool. Without this granular swapping will not work correctly.
130-
if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
131-
existing_pool_config.connection_class = owner_name
133+
if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name)
134+
existing_pool_config.connection_descriptor = owner_name
132135
end
133136

134137
existing_pool_config.pool
@@ -137,7 +140,7 @@ def establish_connection(config, owner_name: Base, role: Base.current_role, shar
137140
pool_manager.set_pool_config(role, shard, pool_config)
138141

139142
payload = {
140-
connection_name: pool_config.connection_name,
143+
connection_name: pool_config.connection_descriptor.name,
141144
role: role,
142145
shard: shard,
143146
config: db_config.configuration_hash
@@ -242,8 +245,8 @@ def get_pool_manager(connection_name)
242245
end
243246

244247
# Get the existing pool manager or initialize and assign a new one.
245-
def set_pool_manager(connection_name)
246-
connection_name_to_pool_manager[connection_name] ||= PoolManager.new
248+
def set_pool_manager(connection_descriptor)
249+
connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
247250
end
248251

249252
def pool_managers
@@ -278,9 +281,9 @@ def resolve_pool_config(config, connection_name, role, shard)
278281

279282
def determine_owner_name(owner_name, config)
280283
if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
281-
StringConnectionName.new(owner_name.to_s)
284+
ConnectionDescriptor.new(owner_name.to_s)
282285
elsif config.is_a?(Symbol)
283-
StringConnectionName.new(config.to_s)
286+
ConnectionDescriptor.new(config.to_s)
284287
else
285288
owner_name
286289
end

activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def schema_reflection
3636
end
3737

3838
def schema_cache; end
39-
def connection_class; end
39+
def connection_descriptor; end
4040
def checkin(_); end
4141
def remove(_); end
4242
def async_executor; end
@@ -364,8 +364,8 @@ def unpin_connection! # :nodoc:
364364
clean
365365
end
366366

367-
def connection_class # :nodoc:
368-
pool_config.connection_class
367+
def connection_descriptor # :nodoc:
368+
pool_config.connection_descriptor
369369
end
370370

371371
# Returns true if there is an open connection being used for the current thread.

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,9 @@ def default_timezone
226226
# the value of +current_preventing_writes+.
227227
def preventing_writes?
228228
return true if replica?
229-
return false if connection_class.nil?
229+
return false if connection_descriptor.nil?
230230

231-
connection_class.current_preventing_writes
231+
connection_descriptor.current_preventing_writes
232232
end
233233

234234
def prepared_statements?
@@ -279,8 +279,8 @@ def lease
279279
@owner = ActiveSupport::IsolatedExecutionState.context
280280
end
281281

282-
def connection_class # :nodoc:
283-
@pool.connection_class
282+
def connection_descriptor # :nodoc:
283+
@pool.connection_descriptor
284284
end
285285

286286
# The role (e.g. +:writing+) for the current connection. In a

activerecord/lib/active_record/connection_adapters/pool_config.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ module ConnectionAdapters
55
class PoolConfig # :nodoc:
66
include MonitorMixin
77

8-
attr_reader :db_config, :role, :shard
8+
attr_reader :db_config, :role, :shard, :connection_descriptor
99
attr_writer :schema_reflection, :server_version
10-
attr_accessor :connection_class
1110

1211
def schema_reflection
1312
@schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path)
@@ -29,7 +28,7 @@ def disconnect_all!
2928
def initialize(connection_class, db_config, role, shard)
3029
super()
3130
@server_version = nil
32-
@connection_class = connection_class
31+
self.connection_descriptor = connection_class
3332
@db_config = db_config
3433
@role = role
3534
@shard = shard
@@ -41,11 +40,12 @@ def server_version(connection)
4140
@server_version || synchronize { @server_version ||= connection.get_database_version }
4241
end
4342

44-
def connection_name
45-
if connection_class.primary_class?
46-
"ActiveRecord::Base"
43+
def connection_descriptor=(connection_descriptor)
44+
case connection_descriptor
45+
when ConnectionHandler::ConnectionDescriptor
46+
@connection_descriptor = connection_descriptor
4747
else
48-
connection_class.name
48+
@connection_descriptor = ConnectionHandler::ConnectionDescriptor.new(connection_descriptor.name, connection_descriptor.primary_class?)
4949
end
5050
end
5151

activerecord/lib/active_record/core.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,17 @@ def self.current_preventing_writes
201201
false
202202
end
203203

204+
# Intended to behave like `.current_preventing_writes` given the class name as input.
205+
# See PoolConfig and ConnectionHandler::ConnectionDescriptor.
206+
def self.preventing_writes?(class_name) # :nodoc:
207+
connected_to_stack.reverse_each do |hash|
208+
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
209+
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].any? { |klass| klass.name == class_name }
210+
end
211+
212+
false
213+
end
214+
204215
def self.connected_to_stack # :nodoc:
205216
if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
206217
connected_to_stack

activerecord/test/cases/connection_adapters/connection_swapping_nested_test.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,34 @@ def test_application_record_prevent_writes_can_be_changed
460460
Object.send(:remove_const, :ApplicationRecord)
461461
ActiveRecord::Base.establish_connection :arunit
462462
end
463+
464+
def test_prevent_writes_handles_class_reloading
465+
# Regression test for https://github.com/rails/rails/issues/54343
466+
Object.const_set(:ReloadedRecord, Class.new(ActiveRecord::Base) { self.abstract_class = true })
467+
ReloadedRecord.connects_to(database: { writing: :arunit, reading: :arunit })
468+
469+
ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true) do
470+
ReloadedRecord.connected_to(role: :writing, prevent_writes: false) do
471+
assert_not_predicate ReloadedRecord.lease_connection, :preventing_writes?
472+
end
473+
end
474+
475+
# emulate a reload in development mode
476+
Object.send(:remove_const, :ReloadedRecord)
477+
Object.const_set(:ReloadedRecord, Class.new(ActiveRecord::Base) { self.abstract_class = true })
478+
ReloadedRecord.connects_to(database: { writing: :arunit, reading: :arunit })
479+
480+
ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true) do
481+
ReloadedRecord.connected_to(role: :writing, prevent_writes: false) do
482+
assert_not_predicate ReloadedRecord.lease_connection, :preventing_writes?
483+
end
484+
end
485+
ensure
486+
ReloadedRecord.remove_connection
487+
ActiveRecord.application_record_class = nil
488+
Object.send(:remove_const, :ReloadedRecord)
489+
ActiveRecord::Base.establish_connection :arunit
490+
end
463491
end
464492
end
465493
end

activerecord/test/cases/multi_db_migrator_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ def puts(*)
7171
def test_schema_migration_is_different_for_different_connections
7272
assert_not_equal @schema_migration_a, @schema_migration_b
7373
assert_not_equal @schema_migration_a.instance_variable_get(:@pool), @schema_migration_b.instance_variable_get(:@pool)
74-
assert_equal "ActiveRecord::Base", @pool_a.pool_config.connection_name
75-
assert_equal "ARUnit2Model", @pool_b.pool_config.connection_name
74+
assert_equal "ActiveRecord::Base", @pool_a.pool_config.connection_descriptor.name
75+
assert_equal "ARUnit2Model", @pool_b.pool_config.connection_descriptor.name
7676
end
7777

7878
def test_finds_migrations

0 commit comments

Comments
 (0)