Skip to content

Commit 9fdaea5

Browse files
authored
Merge pull request rails#52037 from palkan/feat/active-record/nested-pinning
[ActiveRecord] Support nested connection pinning
2 parents 8d416d0 + e44cdcb commit 9fdaea5

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ def initialize(pool_config)
253253

254254
@available = ConnectionLeasingQueue.new self
255255
@pinned_connection = nil
256+
@pinned_connections_depth = 0
256257

257258
@async_executor = build_async_executor
258259

@@ -311,9 +312,9 @@ def connection
311312
end
312313

313314
def pin_connection!(lock_thread) # :nodoc:
314-
raise "There is already a pinned connection" if @pinned_connection
315+
@pinned_connection ||= (connection_lease&.connection || checkout)
316+
@pinned_connections_depth += 1
315317

316-
@pinned_connection = (connection_lease&.connection || checkout)
317318
# Any leased connection must be in @connections otherwise
318319
# some methods like #connected? won't behave correctly
319320
unless @connections.include?(@pinned_connection)
@@ -330,16 +331,22 @@ def unpin_connection! # :nodoc:
330331

331332
clean = true
332333
@pinned_connection.lock.synchronize do
333-
connection, @pinned_connection = @pinned_connection, nil
334+
@pinned_connections_depth -= 1
335+
connection = @pinned_connection
336+
@pinned_connection = nil if @pinned_connections_depth.zero?
337+
334338
if connection.transaction_open?
335339
connection.rollback_transaction
336340
else
337341
# Something committed or rolled back the transaction
338342
clean = false
339343
connection.reset!
340344
end
341-
connection.lock_thread = nil
342-
checkin(connection)
345+
346+
if @pinned_connection.nil?
347+
connection.lock_thread = nil
348+
checkin(connection)
349+
end
343350
end
344351

345352
clean

activerecord/test/cases/connection_pool_test.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,56 @@ def test_unpin_connection_returns_whether_transaction_has_been_rolledback
934934
assert_equal false, @pool.unpin_connection!
935935
end
936936

937+
def test_pin_connection_nesting
938+
assert_instance_of NullTransaction, @pool.lease_connection.current_transaction
939+
@pool.pin_connection!(true)
940+
assert_instance_of RealTransaction, @pool.lease_connection.current_transaction
941+
@pool.pin_connection!(true)
942+
assert_instance_of SavepointTransaction, @pool.lease_connection.current_transaction
943+
@pool.unpin_connection!
944+
assert_instance_of RealTransaction, @pool.lease_connection.current_transaction
945+
@pool.unpin_connection!
946+
assert_instance_of NullTransaction, @pool.lease_connection.current_transaction
947+
948+
assert_raises(RuntimeError, match: /There isn't a pinned connection/) do
949+
@pool.unpin_connection!
950+
end
951+
end
952+
953+
def test_pin_connection_nesting_lock
954+
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
955+
956+
@pool.pin_connection!(true)
957+
actual_lock = @pool.lease_connection.lock
958+
assert_not_equal ActiveSupport::Concurrency::NullLock, actual_lock
959+
960+
@pool.pin_connection!(false)
961+
assert_same actual_lock, @pool.lease_connection.lock
962+
963+
@pool.unpin_connection!
964+
assert_same actual_lock, @pool.lease_connection.lock
965+
966+
@pool.unpin_connection!
967+
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
968+
end
969+
970+
def test_pin_connection_nesting_lock_inverse
971+
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
972+
973+
@pool.pin_connection!(false)
974+
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
975+
976+
@pool.pin_connection!(true)
977+
actual_lock = @pool.lease_connection.lock
978+
assert_not_equal ActiveSupport::Concurrency::NullLock, actual_lock
979+
980+
@pool.unpin_connection!
981+
assert_same actual_lock, @pool.lease_connection.lock # The lock persist until full unpin
982+
983+
@pool.unpin_connection!
984+
assert_equal ActiveSupport::Concurrency::NullLock, @pool.lease_connection.lock
985+
end
986+
937987
private
938988
def active_connections(pool)
939989
pool.connections.find_all(&:in_use?)

0 commit comments

Comments
 (0)