AbstractAdapter: only synchronize when necessary

Ref: https://github.com/rails/rails/pull/28083

Right now all database operations are synchronized with a monitor even though
it's useless in the vast majority of cases.

The synchronization is only useful when transactional fixtures are enabled.

In production, connections are never shared across threads, as such this extra
synchronization is wasteful.
This commit is contained in:
Jean Boussier 2022-11-18 11:48:26 +01:00
parent 625e29211c
commit 1a2ca1920c
3 changed files with 30 additions and 2 deletions

View File

@ -173,6 +173,10 @@ module ActiveRecord
else
@lock_thread = nil
end
if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
active_connection.synchronized = lock_thread
end
end
# Retrieve the connection associated with the current thread, or call
@ -352,7 +356,9 @@ module ActiveRecord
# Raises:
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
def checkout(checkout_timeout = @checkout_timeout)
checkout_and_verify(acquire_connection(checkout_timeout))
connection = checkout_and_verify(acquire_connection(checkout_timeout))
connection.synchronized = @lock_thread
connection
end
# Check-in a database connection back into the pool, indicating that you

View File

@ -4,6 +4,7 @@ require "set"
require "active_record/connection_adapters/sql_type_metadata"
require "active_record/connection_adapters/abstract/schema_dumper"
require "active_record/connection_adapters/abstract/schema_creation"
require "active_support/concurrency/null_lock"
require "active_support/concurrency/load_interlock_aware_monitor"
require "arel/collectors/bind"
require "arel/collectors/composite"
@ -155,7 +156,7 @@ module ActiveRecord
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@visitor = arel_visitor
@statements = build_statement_pool
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
self.synchronized = false
@prepared_statements = self.class.type_cast_config_to_boolean(
@config.fetch(:prepared_statements) { default_prepared_statements }
@ -171,6 +172,14 @@ module ActiveRecord
@verified = false
end
def synchronized=(synchronized) # :nodoc:
@lock = if synchronized
ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
else
ActiveSupport::Concurrency::NullLock
end
end
EXCEPTION_NEVER = { Exception => :never }.freeze # :nodoc:
EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze # :nodoc:
private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module ActiveSupport
module Concurrency
module NullLock # :nodoc:
extend self
def synchronize
yield
end
end
end
end