mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Make clear_active_connections! also return stale connections back to the pool
- also clean up some cruft remaining from per-thread connection cache
This commit is contained in:
parent
212134dce1
commit
d07a6b1a4a
6 changed files with 112 additions and 129 deletions
|
@ -60,11 +60,11 @@ module ActiveRecord
|
||||||
# #connection can be called any number of times; the connection is
|
# #connection can be called any number of times; the connection is
|
||||||
# held in a hash keyed by the thread id.
|
# held in a hash keyed by the thread id.
|
||||||
def connection
|
def connection
|
||||||
if conn = @reserved_connections[active_connection_name]
|
if conn = @reserved_connections[current_connection_id]
|
||||||
conn.verify!(verification_timeout)
|
conn.verify!(verification_timeout)
|
||||||
conn
|
conn
|
||||||
else
|
else
|
||||||
@reserved_connections[active_connection_name] = checkout
|
@reserved_connections[current_connection_id] = checkout
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ module ActiveRecord
|
||||||
# #release_connection releases the connection-thread association
|
# #release_connection releases the connection-thread association
|
||||||
# and returns the connection to the pool.
|
# and returns the connection to the pool.
|
||||||
def release_connection
|
def release_connection
|
||||||
conn = @reserved_connections.delete(active_connection_name)
|
conn = @reserved_connections.delete(current_connection_id)
|
||||||
checkin conn if conn
|
checkin conn if conn
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -118,14 +118,20 @@ module ActiveRecord
|
||||||
# Verify active connections and remove and disconnect connections
|
# Verify active connections and remove and disconnect connections
|
||||||
# associated with stale threads.
|
# associated with stale threads.
|
||||||
def verify_active_connections! #:nodoc:
|
def verify_active_connections! #:nodoc:
|
||||||
remove_stale_cached_threads!(@reserved_connections) do |name, conn|
|
clear_stale_cached_connections!
|
||||||
checkin conn
|
|
||||||
end
|
|
||||||
connections.each do |connection|
|
connections.each do |connection|
|
||||||
connection.verify!(verification_timeout)
|
connection.verify!(verification_timeout)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Return any checked-out connections back to the pool by threads that
|
||||||
|
# are no longer alive.
|
||||||
|
def clear_stale_cached_connections!
|
||||||
|
remove_stale_cached_threads!(@reserved_connections) do |name, conn|
|
||||||
|
checkin conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Check-out a database connection from the pool.
|
# Check-out a database connection from the pool.
|
||||||
def checkout
|
def checkout
|
||||||
raise NotImplementedError, "checkout is an abstract method"
|
raise NotImplementedError, "checkout is an abstract method"
|
||||||
|
@ -156,7 +162,7 @@ module ActiveRecord
|
||||||
ActiveRecord::Base.send(spec.adapter_method, config)
|
ActiveRecord::Base.send(spec.adapter_method, config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def active_connection_name #:nodoc:
|
def current_connection_id #:nodoc:
|
||||||
Thread.current.object_id
|
Thread.current.object_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -178,12 +184,6 @@ module ActiveRecord
|
||||||
# NewConnectionEveryTime is a simple implementation: always
|
# NewConnectionEveryTime is a simple implementation: always
|
||||||
# create/disconnect on checkout/checkin.
|
# create/disconnect on checkout/checkin.
|
||||||
class NewConnectionEveryTime < ConnectionPool
|
class NewConnectionEveryTime < ConnectionPool
|
||||||
def active_connection
|
|
||||||
@reserved_connections[active_connection_name]
|
|
||||||
end
|
|
||||||
|
|
||||||
def active_connections; @reserved_connections; end
|
|
||||||
|
|
||||||
def checkout
|
def checkout
|
||||||
new_connection
|
new_connection
|
||||||
end
|
end
|
||||||
|
@ -288,19 +288,14 @@ module ActiveRecord
|
||||||
@connection_pools[name] = ConnectionAdapters::ConnectionPool.create(spec)
|
@connection_pools[name] = ConnectionAdapters::ConnectionPool.create(spec)
|
||||||
end
|
end
|
||||||
|
|
||||||
# for internal use only and for testing;
|
# Returns any connections in use by the current thread back to the pool,
|
||||||
# only works with ConnectionPerThread pool class
|
# and also returns connections to the pool cached by threads that are no
|
||||||
def active_connections #:nodoc:
|
# longer alive.
|
||||||
@connection_pools.inject({}) do |hash,kv|
|
|
||||||
hash[kv.first] = kv.last.active_connection
|
|
||||||
hash.delete(kv.first) unless hash[kv.first]
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Clears the cache which maps classes to connections.
|
|
||||||
def clear_active_connections!
|
def clear_active_connections!
|
||||||
@connection_pools.each_value {|pool| pool.release_connection }
|
@connection_pools.each_value do |pool|
|
||||||
|
pool.release_connection
|
||||||
|
pool.clear_stale_cached_connections!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Clears the cache which maps classes
|
# Clears the cache which maps classes
|
||||||
|
@ -353,11 +348,9 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
# Apply monitor to all public methods that access the pool.
|
# Apply monitor to all public methods that access the pool.
|
||||||
synchronize :establish_connection, :retrieve_connection,
|
synchronize :establish_connection, :retrieve_connection, :connected?, :remove_connection,
|
||||||
:connected?, :remove_connection, :active_connections,
|
:clear_active_connections!, :clear_reloadable_connections!, :clear_all_connections!,
|
||||||
:clear_active_connections!, :clear_reloadable_connections!,
|
:verify_active_connections!, :with => :connection_pools_lock
|
||||||
:clear_all_connections!, :verify_active_connections!,
|
|
||||||
:with => :connection_pools_lock
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -124,9 +124,8 @@ module ActiveRecord
|
||||||
connection_handler.remove_connection(klass)
|
connection_handler.remove_connection(klass)
|
||||||
end
|
end
|
||||||
|
|
||||||
delegate :active_connections, :clear_active_connections!,
|
delegate :clear_active_connections!, :clear_reloadable_connections!,
|
||||||
:clear_reloadable_connections!, :clear_all_connections!,
|
:clear_all_connections!,:verify_active_connections!, :to => :connection_handler
|
||||||
:verify_active_connections!, :to => :connection_handler
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -280,7 +280,6 @@ unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :OpenBaseAdapter)
|
||||||
sleep zzz # block thread 2 for zzz seconds
|
sleep zzz # block thread 2 for zzz seconds
|
||||||
end
|
end
|
||||||
t1 = Time.now
|
t1 = Time.now
|
||||||
Person.clear_active_connections!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
b = Thread.new do
|
b = Thread.new do
|
||||||
|
@ -288,7 +287,6 @@ unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :OpenBaseAdapter)
|
||||||
t2 = Time.now
|
t2 = Time.now
|
||||||
Person.transaction { yield }
|
Person.transaction { yield }
|
||||||
t3 = Time.now
|
t3 = Time.now
|
||||||
Person.clear_active_connections!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
a.join
|
a.join
|
||||||
|
|
87
activerecord/test/cases/pooled_connections_test.rb
Normal file
87
activerecord/test/cases/pooled_connections_test.rb
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
require "cases/helper"
|
||||||
|
|
||||||
|
class PooledConnectionsTest < ActiveRecord::TestCase
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
@connection = ActiveRecord::Base.remove_connection
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ActiveRecord::Base.clear_all_connections!
|
||||||
|
ActiveRecord::Base.establish_connection(@connection)
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def checkout_connections
|
||||||
|
ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3}))
|
||||||
|
@connections = []
|
||||||
|
@timed_out = 0
|
||||||
|
|
||||||
|
4.times do
|
||||||
|
Thread.new do
|
||||||
|
begin
|
||||||
|
@connections << ActiveRecord::Base.connection_pool.checkout
|
||||||
|
rescue ActiveRecord::ConnectionTimeoutError
|
||||||
|
@timed_out += 1
|
||||||
|
end
|
||||||
|
end.join
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_pooled_connection_checkout
|
||||||
|
checkout_connections
|
||||||
|
assert_equal @connections.length, 2
|
||||||
|
assert_equal @timed_out, 2
|
||||||
|
end
|
||||||
|
|
||||||
|
def checkout_checkin_connections(pool_size, threads)
|
||||||
|
ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :wait_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 test_pooled_connection_checkin_one
|
||||||
|
checkout_checkin_connections 1, 2
|
||||||
|
assert_equal 2, @connection_count
|
||||||
|
assert_equal 0, @timed_out
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_pooled_connection_checkin_two
|
||||||
|
checkout_checkin_connections 2, 3
|
||||||
|
assert_equal 3, @connection_count
|
||||||
|
assert_equal 0, @timed_out
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_pooled_connection_checkout_existing_first
|
||||||
|
ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1}))
|
||||||
|
conn_pool = ActiveRecord::Base.connection_pool
|
||||||
|
conn = conn_pool.checkout
|
||||||
|
conn_pool.checkin(conn)
|
||||||
|
conn = conn_pool.checkout
|
||||||
|
assert ActiveRecord::ConnectionAdapters::AbstractAdapter === conn
|
||||||
|
conn_pool.checkin(conn)
|
||||||
|
end
|
||||||
|
end unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
|
||||||
|
|
||||||
|
class AllowConcurrencyDeprecatedTest < ActiveRecord::TestCase
|
||||||
|
def test_allow_concurrency_is_deprecated
|
||||||
|
assert_deprecated('ActiveRecord::Base.allow_concurrency') do
|
||||||
|
ActiveRecord::Base.allow_concurrency
|
||||||
|
end
|
||||||
|
assert_deprecated('ActiveRecord::Base.allow_concurrency=') do
|
||||||
|
ActiveRecord::Base.allow_concurrency = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,91 +0,0 @@
|
||||||
require "cases/helper"
|
|
||||||
require 'models/topic'
|
|
||||||
require 'models/reply'
|
|
||||||
|
|
||||||
unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
|
|
||||||
class ThreadedConnectionsTest < ActiveRecord::TestCase
|
|
||||||
def test_allow_concurrency_is_deprecated
|
|
||||||
assert_deprecated('ActiveRecord::Base.allow_concurrency') do
|
|
||||||
ActiveRecord::Base.allow_concurrency
|
|
||||||
end
|
|
||||||
assert_deprecated('ActiveRecord::Base.allow_concurrency=') do
|
|
||||||
ActiveRecord::Base.allow_concurrency = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class PooledConnectionsTest < ActiveRecord::TestCase
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
@connection = ActiveRecord::Base.remove_connection
|
|
||||||
end
|
|
||||||
|
|
||||||
def teardown
|
|
||||||
ActiveRecord::Base.clear_all_connections!
|
|
||||||
ActiveRecord::Base.establish_connection(@connection)
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def checkout_connections
|
|
||||||
ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3}))
|
|
||||||
@connections = []
|
|
||||||
@timed_out = 0
|
|
||||||
|
|
||||||
4.times do
|
|
||||||
Thread.new do
|
|
||||||
begin
|
|
||||||
@connections << ActiveRecord::Base.connection_pool.checkout
|
|
||||||
rescue ActiveRecord::ConnectionTimeoutError
|
|
||||||
@timed_out += 1
|
|
||||||
end
|
|
||||||
end.join
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_pooled_connection_checkout
|
|
||||||
checkout_connections
|
|
||||||
assert_equal @connections.length, 2
|
|
||||||
assert_equal @timed_out, 2
|
|
||||||
end
|
|
||||||
|
|
||||||
def checkout_checkin_connections(pool_size, threads)
|
|
||||||
ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :wait_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 test_pooled_connection_checkin_one
|
|
||||||
checkout_checkin_connections 1, 2
|
|
||||||
assert_equal 2, @connection_count
|
|
||||||
assert_equal 0, @timed_out
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_pooled_connection_checkin_two
|
|
||||||
checkout_checkin_connections 2, 3
|
|
||||||
assert_equal 3, @connection_count
|
|
||||||
assert_equal 0, @timed_out
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_pooled_connection_checkout_existing_first
|
|
||||||
ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1}))
|
|
||||||
conn_pool = ActiveRecord::Base.connection_pool
|
|
||||||
conn = conn_pool.checkout
|
|
||||||
conn_pool.checkin(conn)
|
|
||||||
conn = conn_pool.checkout
|
|
||||||
assert ActiveRecord::ConnectionAdapters::AbstractAdapter === conn
|
|
||||||
conn_pool.checkin(conn)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -296,7 +296,6 @@ if current_adapter?(:PostgreSQLAdapter)
|
||||||
topic.approved = !topic.approved?
|
topic.approved = !topic.approved?
|
||||||
topic.save!
|
topic.save!
|
||||||
end
|
end
|
||||||
Topic.clear_active_connections!
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -332,7 +331,6 @@ if current_adapter?(:PostgreSQLAdapter)
|
||||||
dev = Developer.find(1)
|
dev = Developer.find(1)
|
||||||
assert_equal original_salary, dev.salary
|
assert_equal original_salary, dev.salary
|
||||||
end
|
end
|
||||||
Developer.clear_active_connections!
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -344,7 +342,6 @@ if current_adapter?(:PostgreSQLAdapter)
|
||||||
# Always expect original salary.
|
# Always expect original salary.
|
||||||
assert_equal original_salary, Developer.find(1).salary
|
assert_equal original_salary, Developer.find(1).salary
|
||||||
end
|
end
|
||||||
Developer.clear_active_connections!
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue