2011-06-06 14:17:44 -04:00
|
|
|
require "cases/helper"
|
2010-07-16 17:39:40 -04:00
|
|
|
|
|
|
|
module ActiveRecord
|
|
|
|
module ConnectionAdapters
|
|
|
|
class ConnectionPoolTest < ActiveRecord::TestCase
|
2012-03-08 18:40:23 -05:00
|
|
|
attr_reader :pool
|
|
|
|
|
2011-02-04 13:19:02 -05:00
|
|
|
def setup
|
2011-12-30 17:09:39 -05:00
|
|
|
super
|
|
|
|
|
2011-02-04 13:19:02 -05:00
|
|
|
# Keep a duplicate pool so we do not bother others
|
|
|
|
@pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
|
2011-02-11 17:32:21 -05:00
|
|
|
|
|
|
|
if in_memory_db?
|
|
|
|
# Separate connections to an in-memory database create an entirely new database,
|
|
|
|
# with an empty schema etc, so we just stub out this schema on the fly.
|
|
|
|
@pool.with_connection do |connection|
|
|
|
|
connection.create_table :posts do |t|
|
|
|
|
t.integer :cololumn
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2011-02-04 13:19:02 -05:00
|
|
|
end
|
|
|
|
|
2011-12-30 17:09:39 -05:00
|
|
|
def teardown
|
|
|
|
super
|
2012-01-03 14:06:27 -05:00
|
|
|
@pool.disconnect!
|
2011-12-30 17:09:39 -05:00
|
|
|
end
|
|
|
|
|
2012-03-08 18:40:23 -05:00
|
|
|
def active_connections(pool)
|
|
|
|
pool.connections.find_all(&:in_use?)
|
|
|
|
end
|
|
|
|
|
2012-03-12 14:51:28 -04:00
|
|
|
def test_checkout_after_close
|
|
|
|
connection = pool.connection
|
|
|
|
assert connection.in_use?
|
|
|
|
|
|
|
|
connection.close
|
|
|
|
assert !connection.in_use?
|
|
|
|
|
|
|
|
assert pool.connection.in_use?
|
|
|
|
end
|
|
|
|
|
2012-03-08 19:12:49 -05:00
|
|
|
def test_released_connection_moves_between_threads
|
|
|
|
thread_conn = nil
|
|
|
|
|
|
|
|
Thread.new {
|
|
|
|
pool.with_connection do |conn|
|
|
|
|
thread_conn = conn
|
|
|
|
end
|
|
|
|
}.join
|
|
|
|
|
|
|
|
assert thread_conn
|
|
|
|
|
|
|
|
Thread.new {
|
|
|
|
pool.with_connection do |conn|
|
|
|
|
assert_equal thread_conn, conn
|
|
|
|
end
|
|
|
|
}.join
|
|
|
|
end
|
|
|
|
|
2012-03-08 18:40:23 -05:00
|
|
|
def test_with_connection
|
|
|
|
assert_equal 0, active_connections(pool).size
|
|
|
|
|
|
|
|
main_thread = pool.connection
|
|
|
|
assert_equal 1, active_connections(pool).size
|
|
|
|
|
|
|
|
Thread.new {
|
|
|
|
pool.with_connection do |conn|
|
|
|
|
assert conn
|
|
|
|
assert_equal 2, active_connections(pool).size
|
|
|
|
end
|
|
|
|
assert_equal 1, active_connections(pool).size
|
|
|
|
}.join
|
|
|
|
|
|
|
|
main_thread.close
|
|
|
|
assert_equal 0, active_connections(pool).size
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_active_connection_in_use
|
|
|
|
assert !pool.active_connection?
|
|
|
|
main_thread = pool.connection
|
|
|
|
|
|
|
|
assert pool.active_connection?
|
|
|
|
|
|
|
|
main_thread.close
|
|
|
|
|
|
|
|
assert !pool.active_connection?
|
|
|
|
end
|
|
|
|
|
2011-12-30 17:56:26 -05:00
|
|
|
def test_full_pool_exception
|
ConnectionPool, unify exceptions, ConnectionTimeoutError
As a result of different commits, ConnectionPool had become
of two minds about exceptions, sometimes using PoolFullError
and sometimes using ConnectionTimeoutError. In fact, it was
using ConnectionTimeoutError internally, but then recueing
and re-raising as a PoolFullError.
There's no reason for this bifurcation, standardize on
ConnectionTimeoutError, which is the rails2 name and still
accurately describes semantics at this point.
History
In Rails2, ConnectionPool raises a ConnectionTimeoutError if
it can't get a connection within timeout.
Originally in master/rails3, @tenderlove had planned on removing
wait/blocking in connectionpool entirely, at that point he changed
exception to PoolFullError.
But then later wait/blocking came back, but exception remained
PoolFullError.
Then in 02b233556377 pmahoney introduced fair waiting logic, and
brought back ConnectionTimeoutError, introducing the weird bifurcation.
ConnectionTimeoutError accurately describes semantics as of this
point, and is backwards compat with rails2, there's no reason
for PoolFullError to be introduced, and no reason for two
different exception types to be used internally, no reason
to rescue one and re-raise as another. Unify!
2012-09-11 10:38:27 -04:00
|
|
|
assert_raises(ConnectionTimeoutError) do
|
2011-12-30 17:56:26 -05:00
|
|
|
(@pool.size + 1).times do
|
|
|
|
@pool.checkout
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-04-15 21:13:15 -04:00
|
|
|
def test_full_pool_blocks
|
|
|
|
cs = @pool.size.times.map { @pool.checkout }
|
|
|
|
t = Thread.new { @pool.checkout }
|
|
|
|
|
|
|
|
# make sure our thread is in the timeout section
|
|
|
|
Thread.pass until t.status == "sleep"
|
|
|
|
|
|
|
|
connection = cs.first
|
|
|
|
connection.close
|
|
|
|
assert_equal connection, t.join.value
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_removing_releases_latch
|
|
|
|
cs = @pool.size.times.map { @pool.checkout }
|
|
|
|
t = Thread.new { @pool.checkout }
|
|
|
|
|
|
|
|
# make sure our thread is in the timeout section
|
|
|
|
Thread.pass until t.status == "sleep"
|
|
|
|
|
|
|
|
connection = cs.first
|
|
|
|
@pool.remove connection
|
|
|
|
assert_respond_to t.join.value, :execute
|
|
|
|
end
|
|
|
|
|
2011-12-30 17:31:30 -05:00
|
|
|
def test_reap_and_active
|
2011-12-30 17:26:29 -05:00
|
|
|
@pool.checkout
|
|
|
|
@pool.checkout
|
|
|
|
@pool.checkout
|
ConnectionPool wait_timeout no longer used for different types of timeouts. #6441
An AR ConnectionSpec `wait_timeout` is pre-patch used for three
different things:
* mysql2 uses it for MySQL's own wait_timeout (how long MySQL
should allow an idle connection before closing it), and
defaults to 2592000 seconds.
* ConnectionPool uses it for "number of seconds to block and
wait for a connection before giving up and raising a timeout error",
default 5 seconds.
* ConnectionPool uses it for the Reaper, for deciding if a 'dead'
connection can be reaped. Default 5 seconds.
Previously, if you want to change these from defaults, you need
to change them all together. This is problematic _especially_
for the mysql2/ConnectionPool conflict, you will generally _not_
want them to be the same, as evidenced by their wildly different
defaults. This has caused real problems for people #6441 #2894
But as long as we're changing this, forcing renaming the
ConnectionPool key to be more specific, it made sense
to seperate the two ConnectionPool uses too -- these two
types of ConnectionPool timeouts ought to be able to be
changed independently, you won't neccesarily want them
to be the same, even though the defaults are (currently)
the same.
2012-05-23 12:08:11 -04:00
|
|
|
@pool.dead_connection_timeout = 0
|
2011-12-30 17:26:29 -05:00
|
|
|
|
|
|
|
connections = @pool.connections.dup
|
|
|
|
|
|
|
|
@pool.reap
|
|
|
|
|
2011-12-30 17:31:30 -05:00
|
|
|
assert_equal connections.length, @pool.connections.length
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_reap_inactive
|
|
|
|
@pool.checkout
|
|
|
|
@pool.checkout
|
|
|
|
@pool.checkout
|
ConnectionPool wait_timeout no longer used for different types of timeouts. #6441
An AR ConnectionSpec `wait_timeout` is pre-patch used for three
different things:
* mysql2 uses it for MySQL's own wait_timeout (how long MySQL
should allow an idle connection before closing it), and
defaults to 2592000 seconds.
* ConnectionPool uses it for "number of seconds to block and
wait for a connection before giving up and raising a timeout error",
default 5 seconds.
* ConnectionPool uses it for the Reaper, for deciding if a 'dead'
connection can be reaped. Default 5 seconds.
Previously, if you want to change these from defaults, you need
to change them all together. This is problematic _especially_
for the mysql2/ConnectionPool conflict, you will generally _not_
want them to be the same, as evidenced by their wildly different
defaults. This has caused real problems for people #6441 #2894
But as long as we're changing this, forcing renaming the
ConnectionPool key to be more specific, it made sense
to seperate the two ConnectionPool uses too -- these two
types of ConnectionPool timeouts ought to be able to be
changed independently, you won't neccesarily want them
to be the same, even though the defaults are (currently)
the same.
2012-05-23 12:08:11 -04:00
|
|
|
@pool.dead_connection_timeout = 0
|
2011-12-30 17:31:30 -05:00
|
|
|
|
|
|
|
connections = @pool.connections.dup
|
|
|
|
connections.each do |conn|
|
|
|
|
conn.extend(Module.new { def active?; false; end; })
|
|
|
|
end
|
|
|
|
|
|
|
|
@pool.reap
|
|
|
|
|
2011-12-30 17:26:29 -05:00
|
|
|
assert_equal 0, @pool.connections.length
|
|
|
|
ensure
|
2011-12-30 17:31:30 -05:00
|
|
|
connections.each(&:close)
|
2011-12-30 17:26:29 -05:00
|
|
|
end
|
|
|
|
|
2011-12-30 17:09:39 -05:00
|
|
|
def test_remove_connection
|
|
|
|
conn = @pool.checkout
|
|
|
|
assert conn.in_use?
|
|
|
|
|
|
|
|
length = @pool.connections.length
|
|
|
|
@pool.remove conn
|
|
|
|
assert conn.in_use?
|
|
|
|
assert_equal(length - 1, @pool.connections.length)
|
|
|
|
ensure
|
|
|
|
conn.close
|
|
|
|
end
|
|
|
|
|
2011-12-30 17:14:13 -05:00
|
|
|
def test_remove_connection_for_thread
|
|
|
|
conn = @pool.connection
|
|
|
|
@pool.remove conn
|
|
|
|
assert_not_equal(conn, @pool.connection)
|
|
|
|
ensure
|
2012-01-03 14:06:27 -05:00
|
|
|
conn.close if conn
|
2011-12-30 17:14:13 -05:00
|
|
|
end
|
|
|
|
|
2011-03-28 19:43:34 -04:00
|
|
|
def test_active_connection?
|
|
|
|
assert !@pool.active_connection?
|
|
|
|
assert @pool.connection
|
|
|
|
assert @pool.active_connection?
|
|
|
|
@pool.release_connection
|
|
|
|
assert !@pool.active_connection?
|
|
|
|
end
|
|
|
|
|
2010-09-30 02:42:23 -04:00
|
|
|
def test_checkout_behaviour
|
|
|
|
pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
|
|
|
|
connection = pool.connection
|
|
|
|
assert_not_nil connection
|
|
|
|
threads = []
|
|
|
|
4.times do |i|
|
|
|
|
threads << Thread.new(i) do |pool_count|
|
|
|
|
connection = pool.connection
|
|
|
|
assert_not_nil connection
|
2011-12-30 14:37:21 -05:00
|
|
|
connection.close
|
2010-09-30 02:42:23 -04:00
|
|
|
end
|
|
|
|
end
|
2010-11-30 16:38:48 -05:00
|
|
|
|
2011-12-30 14:37:21 -05:00
|
|
|
threads.each(&:join)
|
2010-11-30 16:38:48 -05:00
|
|
|
|
2010-09-30 02:42:23 -04:00
|
|
|
Thread.new do
|
2011-12-30 14:37:21 -05:00
|
|
|
assert pool.connection
|
2011-11-29 18:04:41 -05:00
|
|
|
pool.connection.close
|
|
|
|
end.join
|
2010-09-30 02:42:23 -04:00
|
|
|
end
|
2011-02-04 18:54:32 -05:00
|
|
|
|
2012-05-25 14:19:19 -04:00
|
|
|
# The connection pool is "fair" if threads waiting for
|
|
|
|
# connections receive them the order in which they began
|
|
|
|
# waiting. This ensures that we don't timeout one HTTP request
|
|
|
|
# even while well under capacity in a multi-threaded environment
|
|
|
|
# such as a Java servlet container.
|
|
|
|
#
|
|
|
|
# We don't need strict fairness: if two connections become
|
|
|
|
# available at the same time, it's fine of two threads that were
|
|
|
|
# waiting acquire the connections out of order.
|
|
|
|
#
|
|
|
|
# Thus this test prepares waiting threads and then trickles in
|
|
|
|
# available connections slowly, ensuring the wakeup order is
|
|
|
|
# correct in this case.
|
|
|
|
def test_checkout_fairness
|
|
|
|
@pool.instance_variable_set(:@size, 10)
|
|
|
|
expected = (1..@pool.size).to_a.freeze
|
|
|
|
# check out all connections so our threads start out waiting
|
|
|
|
conns = expected.map { @pool.checkout }
|
|
|
|
mutex = Mutex.new
|
|
|
|
order = []
|
|
|
|
errors = []
|
|
|
|
|
|
|
|
threads = expected.map do |i|
|
|
|
|
t = Thread.new {
|
|
|
|
begin
|
2012-06-11 20:54:12 -04:00
|
|
|
@pool.checkout # never checked back in
|
2012-05-25 14:19:19 -04:00
|
|
|
mutex.synchronize { order << i }
|
|
|
|
rescue => e
|
|
|
|
mutex.synchronize { errors << e }
|
|
|
|
end
|
|
|
|
}
|
|
|
|
Thread.pass until t.status == "sleep"
|
|
|
|
t
|
|
|
|
end
|
|
|
|
|
|
|
|
# this should wake up the waiting threads one by one in order
|
|
|
|
conns.each { |conn| @pool.checkin(conn); sleep 0.1 }
|
|
|
|
|
|
|
|
threads.each(&:join)
|
|
|
|
|
|
|
|
raise errors.first if errors.any?
|
|
|
|
|
|
|
|
assert_equal(expected, order)
|
|
|
|
end
|
|
|
|
|
|
|
|
# As mentioned in #test_checkout_fairness, we don't care about
|
|
|
|
# strict fairness. This test creates two groups of threads:
|
|
|
|
# group1 whose members all start waiting before any thread in
|
|
|
|
# group2. Enough connections are checked in to wakeup all
|
|
|
|
# group1 threads, and the fact that only group1 and no group2
|
|
|
|
# threads acquired a connection is enforced.
|
|
|
|
def test_checkout_fairness_by_group
|
|
|
|
@pool.instance_variable_set(:@size, 10)
|
|
|
|
# take all the connections
|
|
|
|
conns = (1..10).map { @pool.checkout }
|
|
|
|
mutex = Mutex.new
|
|
|
|
successes = [] # threads that successfully got a connection
|
|
|
|
errors = []
|
|
|
|
|
|
|
|
make_thread = proc do |i|
|
|
|
|
t = Thread.new {
|
|
|
|
begin
|
2012-06-11 20:54:12 -04:00
|
|
|
@pool.checkout # never checked back in
|
2012-05-25 14:19:19 -04:00
|
|
|
mutex.synchronize { successes << i }
|
|
|
|
rescue => e
|
|
|
|
mutex.synchronize { errors << e }
|
|
|
|
end
|
|
|
|
}
|
|
|
|
Thread.pass until t.status == "sleep"
|
|
|
|
t
|
|
|
|
end
|
|
|
|
|
|
|
|
# all group1 threads start waiting before any in group2
|
|
|
|
group1 = (1..5).map(&make_thread)
|
|
|
|
group2 = (6..10).map(&make_thread)
|
|
|
|
|
|
|
|
# checkin n connections back to the pool
|
|
|
|
checkin = proc do |n|
|
|
|
|
n.times do
|
|
|
|
c = conns.pop
|
|
|
|
@pool.checkin(c)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
checkin.call(group1.size) # should wake up all group1
|
|
|
|
|
|
|
|
loop do
|
|
|
|
sleep 0.1
|
|
|
|
break if mutex.synchronize { (successes.size + errors.size) == group1.size }
|
|
|
|
end
|
|
|
|
|
|
|
|
winners = mutex.synchronize { successes.dup }
|
|
|
|
checkin.call(group2.size) # should wake up everyone remaining
|
|
|
|
|
|
|
|
group1.each(&:join)
|
|
|
|
group2.each(&:join)
|
|
|
|
|
|
|
|
assert_equal((1..group1.size).to_a, winners.sort)
|
|
|
|
|
|
|
|
if errors.any?
|
|
|
|
raise errors.first
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-02-04 18:54:32 -05:00
|
|
|
def test_automatic_reconnect=
|
|
|
|
pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
|
|
|
|
assert pool.automatic_reconnect
|
|
|
|
assert pool.connection
|
|
|
|
|
|
|
|
pool.disconnect!
|
|
|
|
assert pool.connection
|
|
|
|
|
|
|
|
pool.disconnect!
|
|
|
|
pool.automatic_reconnect = false
|
|
|
|
|
|
|
|
assert_raises(ConnectionNotEstablished) do
|
|
|
|
pool.connection
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_raises(ConnectionNotEstablished) do
|
|
|
|
pool.with_connection
|
|
|
|
end
|
|
|
|
end
|
2011-08-08 18:27:54 -04:00
|
|
|
|
|
|
|
def test_pool_sets_connection_visitor
|
|
|
|
assert @pool.connection.visitor.is_a?(Arel::Visitors::ToSql)
|
|
|
|
end
|
2010-07-16 17:39:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|