Skip to content

Commit 7239ec1

Browse files
authored
Merge pull request rails#47972 from Shopify/ar-get-version-schema-cache
Active Record: assign connection pool before checking version
2 parents 539144d + f831111 commit 7239ec1

File tree

6 files changed

+55
-23
lines changed

6 files changed

+55
-23
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -684,9 +684,10 @@ def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
684684
alias_method :release, :remove_connection_from_thread_cache
685685

686686
def new_connection
687-
Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
688-
conn.check_version
689-
end
687+
connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
688+
connection.pool = self
689+
connection.check_version
690+
connection
690691
end
691692

692693
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting

activerecord/test/cases/connection_adapters/connection_handler_test.rb

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,9 @@ def test_forked_child_doesnt_mangle_parent_connection
354354

355355
pid = fork {
356356
rd.close
357-
if ActiveRecord::Base.connection.active?
358-
wr.write Marshal.dump ActiveRecord::Base.connection.object_id
359-
end
357+
wr.write Marshal.dump [
358+
ActiveRecord::Base.connection.object_id,
359+
]
360360
wr.close
361361

362362
exit # allow finalizers to run
@@ -365,7 +365,8 @@ def test_forked_child_doesnt_mangle_parent_connection
365365
wr.close
366366

367367
Process.waitpid pid
368-
assert_not_equal object_id, Marshal.load(rd.read)
368+
child_id = Marshal.load(rd.read)
369+
assert_not_equal object_id, child_id
369370
rd.close
370371

371372
assert_equal 3, ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people")
@@ -385,11 +386,11 @@ def test_forked_child_recovers_from_disconnected_parent
385386

386387
pid = fork {
387388
rd.close
388-
if ActiveRecord::Base.connection.active?
389-
pair = [ActiveRecord::Base.connection.object_id,
390-
ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people")]
391-
wr.write Marshal.dump pair
392-
end
389+
wr.write Marshal.dump [
390+
!!ActiveRecord::Base.connection.active?,
391+
ActiveRecord::Base.connection.object_id,
392+
ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people"),
393+
]
393394
wr.close
394395

395396
exit # allow finalizers to run
@@ -401,8 +402,9 @@ def test_forked_child_recovers_from_disconnected_parent
401402
wr.close
402403

403404
Process.waitpid outer_pid
404-
child_id, child_count = Marshal.load(rd.read)
405+
active, child_id, child_count = Marshal.load(rd.read)
405406

407+
assert_equal false, active
406408
assert_not_equal object_id, child_id
407409
rd.close
408410

activerecord/test/cases/connection_pool_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ def test_with_connection
8383
assert_equal 0, active_connections(pool).size
8484
end
8585

86+
def test_new_connection_no_query
87+
skip("Can't test with in-memory dbs") if in_memory_db?
88+
assert_equal 0, pool.connections.size
89+
pool.with_connection { |_conn| } # warm the schema cache
90+
pool.flush(0)
91+
assert_equal 0, pool.connections.size
92+
93+
assert_no_queries do
94+
pool.with_connection { |_conn| }
95+
end
96+
end
97+
8698
def test_active_connection_in_use
8799
assert_not_predicate pool, :active_connection?
88100
main_thread = pool.connection

activerecord/test/cases/reaper_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ def new_conn_in_thread(pool)
180180

181181
child = Thread.new do
182182
conn = pool.checkout
183+
conn.query("SELECT 1") # ensure connected
183184
event.set
184185
Thread.stop
185186
end

railties/test/application/initializers/frameworks_test.rb

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ def show
219219
test "can boot with an unhealthy database" do
220220
rails %w(generate model post title:string)
221221

222-
switch_env("DATABASE_URL", "mysql2://127.0.0.1:1") do
222+
with_unhealthy_database do
223223
require "#{app_path}/config/environment"
224224
end
225225
end
@@ -261,16 +261,18 @@ def show
261261
config.eager_load = true
262262
RUBY
263263

264-
switch_env("DATABASE_URL", "mysql2://127.0.0.1:1") do
265-
# The existing schema cache dump will contain ActiveRecord::ConnectionAdapters::SQLite3::Column objects
266-
require "active_record/connection_adapters/sqlite3/column"
267-
264+
with_unhealthy_database do
268265
require "#{app_path}/config/environment"
269266

270-
assert_nil ActiveRecord::Base.connection_pool.schema_cache
267+
assert_not_nil ActiveRecord::Base.connection_pool.schema_cache
268+
271269
assert_raises ActiveRecord::ConnectionNotEstablished do
272270
ActiveRecord::Base.connection.execute("SELECT 1")
273271
end
272+
273+
assert_raises ActiveRecord::ConnectionNotEstablished do
274+
ActiveRecord::Base.connection_pool.schema_cache.columns("posts")
275+
end
274276
end
275277
end
276278

@@ -296,10 +298,7 @@ def show
296298
config.active_record.check_schema_cache_dump_version = false
297299
RUBY
298300

299-
switch_env("DATABASE_URL", "mysql2://127.0.0.1:1") do
300-
# The existing schema cache dump will contain ActiveRecord::ConnectionAdapters::SQLite3::Column objects
301-
require "active_record/connection_adapters/sqlite3/column"
302-
301+
with_unhealthy_database do
303302
require "#{app_path}/config/environment"
304303

305304
assert ActiveRecord::Base.connection_pool.schema_cache.data_sources("posts")

railties/test/isolation/abstract_unit.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,23 @@ def multi_db_database_configs
227227
YAML
228228
end
229229

230+
def with_unhealthy_database(&block)
231+
# The existing schema cache dump will contain ActiveRecord::ConnectionAdapters::SQLite3Adapter objects
232+
require "active_record/connection_adapters/sqlite3_adapter"
233+
234+
# We need to change the `database_version` to match what is expected for MySQL
235+
dump_path = File.join(app_path, "db/schema_cache.yml")
236+
if File.exist?(dump_path)
237+
schema_cache = ActiveRecord::ConnectionAdapters::SchemaCache.load_from(dump_path)
238+
schema_cache.connection = Struct.new(:schema_version).new(schema_cache.version)
239+
schema_cache.instance_variable_set(:@database_version, ActiveRecord::ConnectionAdapters::AbstractAdapter::Version.new("8.8.8"))
240+
File.write(dump_path, YAML.dump(schema_cache))
241+
end
242+
243+
# We load the app while pointing at a non-existing MySQL server
244+
switch_env("DATABASE_URL", "mysql2://127.0.0.1:1", &block)
245+
end
246+
230247
# Make a very basic app, without creating the whole directory structure.
231248
# This is faster and simpler than the method above.
232249
def make_basic_app

0 commit comments

Comments
 (0)