Skip to content

Commit

Permalink
Merge pull request rails#52037 from palkan/feat/active-record/nested-…
Browse files Browse the repository at this point in the history
…pinning

[ActiveRecord] Support nested connection pinning
  • Loading branch information
byroot committed Jun 8, 2024
1 parent 0b18f0b commit 5f7dcb4
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ def initialize(pool_config)

@available = ConnectionLeasingQueue.new self
@pinned_connection = nil
@pinned_connections_depth = 0

@async_executor = build_async_executor

Expand Down Expand Up @@ -311,9 +312,9 @@ def connection
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)
Expand All @@ -330,16 +331,22 @@ def unpin_connection! # :nodoc:

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
# Something committed or rolled back the transaction
clean = false
connection.reset!
end
connection.lock_thread = nil
checkin(connection)

if @pinned_connection.nil?
connection.lock_thread = nil
checkin(connection)
end
end

clean
Expand Down
50 changes: 50 additions & 0 deletions activerecord/test/cases/connection_pool_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,56 @@ def test_unpin_connection_returns_whether_transaction_has_been_rolledback
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?)
Expand Down

0 comments on commit 5f7dcb4

Please sign in to comment.