feat: support nested connection pinning

This commit is contained in:
Vladimir Dementyev 2024-06-06 13:55:09 -07:00 committed by Jean Boussier
parent 7dbe81710e
commit e44cdcb7a7
2 changed files with 62 additions and 5 deletions

View File

@ -253,6 +253,7 @@ module ActiveRecord
@available = ConnectionLeasingQueue.new self
@pinned_connection = nil
@pinned_connections_depth = 0
@async_executor = build_async_executor
@ -311,9 +312,9 @@ module ActiveRecord
end
def pin_connection!(lock_thread) # :nodoc:
raise "There is already a pinned connection" if @pinned_connection
@pinned_connection ||= (connection_lease&.connection || checkout)
@pinned_connections_depth += 1
@pinned_connection = (connection_lease&.connection || checkout)
# Any leased connection must be in @connections otherwise
# some methods like #connected? won't behave correctly
unless @connections.include?(@pinned_connection)
@ -330,7 +331,10 @@ module ActiveRecord
clean = true
@pinned_connection.lock.synchronize do
connection, @pinned_connection = @pinned_connection, nil
@pinned_connections_depth -= 1
connection = @pinned_connection
@pinned_connection = nil if @pinned_connections_depth.zero?
if connection.transaction_open?
connection.rollback_transaction
else
@ -338,8 +342,11 @@ module ActiveRecord
clean = false
connection.reset!
end
connection.lock_thread = nil
checkin(connection)
if @pinned_connection.nil?
connection.lock_thread = nil
checkin(connection)
end
end
clean

View File

@ -934,6 +934,56 @@ module ActiveRecord
assert_equal false, @pool.unpin_connection!
end
def test_pin_connection_nesting
assert_instance_of NullTransaction, @pool.lease_connection.current_transaction
@pool.pin_connection!(true)
assert_instance_of RealTransaction, @pool.lease_connection.current_transaction
@pool.pin_connection!(true)
assert_instance_of SavepointTransaction, @pool.lease_connection.current_transaction
@pool.unpin_connection!
assert_instance_of RealTransaction, @pool.lease_connection.current_transaction
@pool.unpin_connection!
assert_instance_of NullTransaction, @pool.lease_connection.current_transaction
assert_raises(RuntimeError, match: /There isn't a pinned connection/) do
@pool.unpin_connection!
end
end
def test_pin_connection_nesting_lock
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
@pool.pin_connection!(true)
actual_lock = @pool.lease_connection.lock
assert_not_equal ActiveSupport::Concurrency::NullLock, actual_lock
@pool.pin_connection!(false)
assert_same actual_lock, @pool.lease_connection.lock
@pool.unpin_connection!
assert_same actual_lock, @pool.lease_connection.lock
@pool.unpin_connection!
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
end
def test_pin_connection_nesting_lock_inverse
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
@pool.pin_connection!(false)
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
@pool.pin_connection!(true)
actual_lock = @pool.lease_connection.lock
assert_not_equal ActiveSupport::Concurrency::NullLock, actual_lock
@pool.unpin_connection!
assert_same actual_lock, @pool.lease_connection.lock # The lock persist until full unpin
@pool.unpin_connection!
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
end
private
def active_connections(pool)
pool.connections.find_all(&:in_use?)