aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrent Wheeldon <brent.wheeldon@gmail.com>2018-01-12 15:58:04 -0500
committerBrent Wheeldon <brent.wheeldon@gmail.com>2018-03-23 15:53:19 -0400
commit0a1ed447999d0092e8d0e86729666fc3b4577151 (patch)
treecf67e13a66869acc5cb10bf8357da272340c3f74
parent6aa5cf03ea8232180ffbbae4c130b051f813c670 (diff)
downloadrails-0a1ed447999d0092e8d0e86729666fc3b4577151.tar.gz
rails-0a1ed447999d0092e8d0e86729666fc3b4577151.tar.bz2
rails-0a1ed447999d0092e8d0e86729666fc3b4577151.zip
Prevent deadlocks when waiting for connection from pool.
When a thread that had the load interlock but was blocked waiting to check a connection out of the connection pool but all of the threads using the available connections were blocked waiting to obtain the load interlock an `ActiveRecord::ConnectionTimeoutError` exception was be thrown by the thread waiting for the connection. When waiting for the connection to check out we should allow loading to proceed to avoid this deadlock.
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/test/cases/connection_pool_test.rb38
2 files changed, 41 insertions, 1 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index c730584902..be999e7749 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -188,7 +188,9 @@ module ActiveRecord
t0 = Time.now
elapsed = 0
loop do
- @cond.wait(timeout - elapsed)
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @cond.wait(timeout - elapsed)
+ end
return remove if any?
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 9ac03629c3..b02d7fb4fd 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -109,6 +109,44 @@ module ActiveRecord
assert_equal connection, t.join.value
end
+ def test_full_pool_blocking_shares_load_interlock
+ @pool.instance_variable_set(:@size, 1)
+
+ load_interlock_latch = Concurrent::CountDownLatch.new
+ connection_latch = Concurrent::CountDownLatch.new
+
+ able_to_get_connection = false
+ able_to_load = false
+
+ thread_with_load_interlock = Thread.new do
+ ActiveSupport::Dependencies.interlock.running do
+ load_interlock_latch.count_down
+ connection_latch.wait
+
+ @pool.with_connection do
+ able_to_get_connection = true
+ end
+ end
+ end
+
+ thread_with_last_connection = Thread.new do
+ @pool.with_connection do
+ connection_latch.count_down
+ load_interlock_latch.wait
+
+ ActiveSupport::Dependencies.interlock.loading do
+ able_to_load = true
+ end
+ end
+ end
+
+ thread_with_load_interlock.join
+ thread_with_last_connection.join
+
+ assert able_to_get_connection
+ assert able_to_load
+ end
+
def test_removing_releases_latch
cs = @pool.size.times.map { @pool.checkout }
t = Thread.new { @pool.checkout }