diff options
author | Brent Wheeldon <brent.wheeldon@gmail.com> | 2018-01-12 15:58:04 -0500 |
---|---|---|
committer | Brent Wheeldon <brent.wheeldon@gmail.com> | 2018-03-23 15:53:19 -0400 |
commit | 0a1ed447999d0092e8d0e86729666fc3b4577151 (patch) | |
tree | cf67e13a66869acc5cb10bf8357da272340c3f74 | |
parent | 6aa5cf03ea8232180ffbbae4c130b051f813c670 (diff) | |
download | rails-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.rb | 4 | ||||
-rw-r--r-- | activerecord/test/cases/connection_pool_test.rb | 38 |
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 } |