Skip to content

Commit afefa50

Browse files
committed
Retry new connection on checkout after reap
When we reap connections, we check if they are inactive (connected and responding to ping in most adapters) and if so we remove the connection instead of checking it back in. However, in acquire_connection, we weren't checking after reaping whether we were allowed to build a new connection, only whether an existing one was in the available pool. This still leaves a race condition where if the background reaper thread runs while a thread is polling and finds a free inactive connection, it could have the same issue and not wake the waiting thread. However we don't really expect the reaper to solve this (it only runs every 60 seconds by default, far too slow to solve for a blocked thread). I think this should be fixed, just separately.
1 parent a4d5f1d commit afefa50

File tree

2 files changed

+31
-1
lines changed

2 files changed

+31
-1
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,13 @@ def acquire_connection(checkout_timeout)
668668
conn
669669
else
670670
reap
671-
@available.poll(checkout_timeout)
671+
# Retry after reaping, which may return an available connection,
672+
# remove an inactive connection, or both
673+
if conn = @available.poll || try_to_checkout_new_connection
674+
conn
675+
else
676+
@available.poll(checkout_timeout)
677+
end
672678
end
673679
end
674680

activerecord/test/cases/connection_pool_test.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,30 @@ def test_reap_inactive
215215
@pool.connections.each { |conn| conn.close if conn.in_use? }
216216
end
217217

218+
def test_inactive_are_returned_from_dead_thread
219+
ready = Concurrent::CountDownLatch.new
220+
@pool.instance_variable_set(:@size, 1)
221+
222+
child = new_thread do
223+
@pool.checkout
224+
ready.count_down
225+
stop_thread
226+
end
227+
228+
pass_to(child) until ready.wait(0)
229+
230+
assert_equal 1, active_connections(@pool).size
231+
232+
child.terminate
233+
child.join
234+
235+
@pool.checkout
236+
237+
assert_equal 1, active_connections(@pool).size
238+
ensure
239+
@pool.connections.each { |conn| conn.close if conn.in_use? }
240+
end
241+
218242
def test_idle_timeout_configuration
219243
@pool.disconnect!
220244

0 commit comments

Comments
 (0)