mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
5e024070ab
The code in `ConnectionPool#release` assumed that a single thread only ever holds a single connection, and thus that releasing a connection only requires the owning thread_id. There is a trivial counterexample to this assumption: code that checks out additional connections from the pool in the same thread. For instance: connection_1 = ActiveRecord::Base.connection connection_2 = ActiveRecord::Base.connection_pool.checkout ActiveRecord::Base.connection_pool.checkin(connection_2) connection_3 = ActiveRecord::Base.connection At this point, connection_1 has been removed from the `@reserved_connections` hash, causing a NEW connection to be returned as connection_3 and the loss of any tracking info on connection_1. As long as the thread in this example lives, connection_1 will be inaccessible and un-reapable. If this block of code runs more times than the size of the connection pool in a single thread, every subsequent connection attempt will timeout, as all of the available connections have been leaked. Reverts parts of9e457a8654
and essentially all of4367d2f05c
81 lines
2.5 KiB
Ruby
81 lines
2.5 KiB
Ruby
require "cases/helper"
|
|
require "models/project"
|
|
require "timeout"
|
|
|
|
class PooledConnectionsTest < ActiveRecord::TestCase
|
|
self.use_transactional_fixtures = false
|
|
|
|
def setup
|
|
@per_test_teardown = []
|
|
@connection = ActiveRecord::Base.remove_connection
|
|
end
|
|
|
|
teardown do
|
|
ActiveRecord::Base.clear_all_connections!
|
|
ActiveRecord::Base.establish_connection(@connection)
|
|
@per_test_teardown.each(&:call)
|
|
end
|
|
|
|
# Will deadlock due to lack of Monitor timeouts in 1.9
|
|
def checkout_checkin_connections(pool_size, threads)
|
|
ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5}))
|
|
@connection_count = 0
|
|
@timed_out = 0
|
|
threads.times do
|
|
Thread.new do
|
|
begin
|
|
conn = ActiveRecord::Base.connection_pool.checkout
|
|
sleep 0.1
|
|
ActiveRecord::Base.connection_pool.checkin conn
|
|
@connection_count += 1
|
|
rescue ActiveRecord::ConnectionTimeoutError
|
|
@timed_out += 1
|
|
end
|
|
end.join
|
|
end
|
|
end
|
|
|
|
def checkout_checkin_connections_loop(pool_size, loops)
|
|
ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5}))
|
|
@connection_count = 0
|
|
@timed_out = 0
|
|
loops.times do
|
|
begin
|
|
conn = ActiveRecord::Base.connection_pool.checkout
|
|
ActiveRecord::Base.connection_pool.checkin conn
|
|
@connection_count += 1
|
|
ActiveRecord::Base.connection.tables
|
|
rescue ActiveRecord::ConnectionTimeoutError
|
|
@timed_out += 1
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_pooled_connection_checkin_one
|
|
checkout_checkin_connections 1, 2
|
|
assert_equal 2, @connection_count
|
|
assert_equal 0, @timed_out
|
|
assert_equal 1, ActiveRecord::Base.connection_pool.connections.size
|
|
end
|
|
|
|
def test_pooled_connection_checkin_two
|
|
checkout_checkin_connections_loop 2, 3
|
|
assert_equal 3, @connection_count
|
|
assert_equal 0, @timed_out
|
|
assert_equal 2, ActiveRecord::Base.connection_pool.connections.size
|
|
end
|
|
|
|
def test_pooled_connection_remove
|
|
ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.5}))
|
|
old_connection = ActiveRecord::Base.connection
|
|
extra_connection = ActiveRecord::Base.connection_pool.checkout
|
|
ActiveRecord::Base.connection_pool.remove(extra_connection)
|
|
assert_equal ActiveRecord::Base.connection, old_connection
|
|
end
|
|
|
|
private
|
|
|
|
def add_record(name)
|
|
ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name }
|
|
end
|
|
end unless in_memory_db?
|