未验证 提交 3ba20c7f 编写于 作者: M Matthew Draper 提交者: GitHub

Merge pull request #31221 from matthewd/flush-idle-connections

Flush idle database connections
......@@ -63,15 +63,13 @@ module ConnectionAdapters
# There are several connection-pooling-related options that you can add to
# your database connection configuration:
#
# * +pool+: number indicating size of connection pool (default 5)
# * +checkout_timeout+: number of seconds to block and wait for a connection
# before giving up and raising a timeout error (default 5 seconds).
# * +reaping_frequency+: frequency in seconds to periodically run the
# Reaper, which attempts to find and recover connections from dead
# threads, which can occur if a programmer forgets to close a
# connection at the end of a thread or a thread dies unexpectedly.
# Regardless of this setting, the Reaper will be invoked before every
# blocking wait. (Default +nil+, which means don't schedule the Reaper).
# * +pool+: maximum number of connections the pool may manage (default 5).
# * +idle_timeout+: number of seconds that a connection will be kept
# unused in the pool before it is automatically disconnected (default
# 300 seconds). Set this to zero to keep connections forever.
# * +checkout_timeout+: number of seconds to wait for a connection to
# become available before giving up and raising a timeout error (default
# 5 seconds).
#
#--
# Synchronization policy:
......@@ -280,12 +278,12 @@ def internal_poll(timeout)
end
end
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
# A reaper instantiated with a +nil+ frequency will never reap the
# connection pool.
# Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
# +pool+. A reaper instantiated with a zero frequency will never reap
# the connection pool.
#
# Configure the frequency by setting "reaping_frequency" in your
# database yaml file.
# Configure the frequency by setting +reaping_frequency+ in your database
# yaml file (default 60 seconds).
class Reaper
attr_reader :pool, :frequency
......@@ -295,11 +293,12 @@ def initialize(pool, frequency)
end
def run
return unless frequency
return unless frequency && frequency > 0
Thread.new(frequency, pool) { |t, p|
loop do
sleep t
p.reap
p.flush
end
}
end
......@@ -323,6 +322,10 @@ def initialize(spec)
@spec = spec
@checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
if @idle_timeout = spec.config.fetch(:idle_timeout, 300)
@idle_timeout = @idle_timeout.to_f
@idle_timeout = nil if @idle_timeout <= 0
end
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
......@@ -353,7 +356,10 @@ def initialize(spec)
@lock_thread = false
@reaper = Reaper.new(self, spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f)
# +reaping_frequency+ is configurable mostly for historical reasons, but it could
# also be useful if someone wants a very low +idle_timeout+.
reaping_frequency = spec.config.fetch(:reaping_frequency, 60)
@reaper = Reaper.new(self, reaping_frequency && reaping_frequency.to_f)
@reaper.run
end
......@@ -587,6 +593,35 @@ def reap
end
end
# Disconnect all connections that have been idle for at least
# +minimum_idle+ seconds. Connections currently checked out, or that were
# checked in less than +minimum_idle+ seconds ago, are unaffected.
def flush(minimum_idle = @idle_timeout)
return if minimum_idle.nil?
idle_connections = synchronize do
@connections.select do |conn|
!conn.in_use? && conn.seconds_idle >= minimum_idle
end.each do |conn|
conn.lease
@available.delete conn
@connections.delete conn
end
end
idle_connections.each do |conn|
conn.disconnect!
end
end
# Disconnect all currently idle connections. Connections currently checked
# out are unaffected.
def flush!
reap
flush(-1)
end
def num_waiting_in_queue # :nodoc:
@available.num_waiting
end
......@@ -956,6 +991,13 @@ def clear_all_connections!
connection_pool_list.each(&:disconnect!)
end
# Disconnects all currently idle connections.
#
# See ConnectionPool#flush! for details.
def flush_idle_connections!
connection_pool_list.each(&:flush!)
end
# Locate the connection of the nearest super class. This can be an
# active or defined connection: if it is the latter, it will be
# opened and set as the active connection for the class it was defined
......
......@@ -105,6 +105,7 @@ def initialize(connection, logger = nil, config = {}) # :nodoc:
@logger = logger
@config = config
@pool = nil
@idle_since = Concurrent.monotonic_time
@schema_cache = SchemaCache.new self
@quoted_column_names, @quoted_table_names = {}, {}
@visitor = arel_visitor
......@@ -164,6 +165,7 @@ def expire
"Current thread: #{Thread.current}."
end
@idle_since = Concurrent.monotonic_time
@owner = nil
else
raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
......@@ -183,6 +185,12 @@ def steal! # :nodoc:
end
end
# Seconds since this connection was returned to the pool
def seconds_idle # :nodoc:
return 0 if in_use?
Concurrent.monotonic_time - @idle_since
end
def unprepared_statement
old_prepared_statements, @prepared_statements = @prepared_statements, false
yield
......
......@@ -140,6 +140,6 @@ def clear_cache! # :nodoc:
end
delegate :clear_active_connections!, :clear_reloadable_connections!,
:clear_all_connections!, to: :connection_handler
:clear_all_connections!, :flush_idle_connections!, to: :connection_handler
end
end
......@@ -177,7 +177,16 @@ class Railtie < Rails::Railtie # :nodoc:
initializer "active_record.clear_active_connections" do
config.after_initialize do
ActiveSupport.on_load(:active_record) do
# Ideally the application doesn't connect to the database during boot,
# but sometimes it does. In case it did, we want to empty out the
# connection pools so that a non-database-using process (e.g. a master
# process in a forking server model) doesn't retain a needless
# connection. If it was needed, the incremental cost of reestablishing
# this connection is trivial: the rest of the pool would need to be
# populated anyway.
clear_active_connections!
flush_idle_connections!
end
end
end
......
......@@ -156,6 +156,53 @@ def test_reap_inactive
@pool.connections.each { |conn| conn.close if conn.in_use? }
end
def test_flush
idle_conn = @pool.checkout
recent_conn = @pool.checkout
active_conn = @pool.checkout
@pool.checkin idle_conn
@pool.checkin recent_conn
assert_equal 3, @pool.connections.length
def idle_conn.seconds_idle
1000
end
@pool.flush(30)
assert_equal 2, @pool.connections.length
assert_equal [recent_conn, active_conn].sort_by(&:__id__), @pool.connections.sort_by(&:__id__)
ensure
@pool.checkin active_conn
end
def test_flush_bang
idle_conn = @pool.checkout
recent_conn = @pool.checkout
active_conn = @pool.checkout
_dead_conn = Thread.new { @pool.checkout }.join
@pool.checkin idle_conn
@pool.checkin recent_conn
assert_equal 4, @pool.connections.length
def idle_conn.seconds_idle
1000
end
@pool.flush!
assert_equal 1, @pool.connections.length
assert_equal [active_conn].sort_by(&:__id__), @pool.connections.sort_by(&:__id__)
ensure
@pool.checkin active_conn
end
def test_remove_connection
conn = @pool.checkout
assert conn.in_use?
......
......@@ -18,6 +18,7 @@ def setup
class FakePool
attr_reader :reaped
attr_reader :flushed
def initialize
@reaped = false
......@@ -26,6 +27,10 @@ def initialize
def reap
@reaped = true
end
def flush
@flushed = true
end
end
# A reaper with nil time should never reap connections
......@@ -47,6 +52,7 @@ def test_some_time
Thread.pass
end
assert fp.reaped
assert fp.flushed
end
def test_pool_has_reaper
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册