aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb188
1 files changed, 104 insertions, 84 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index b5a8011ca4..3a9db4a109 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,7 +1,8 @@
require 'thread'
+require 'thread_safe'
require 'monitor'
require 'set'
-require 'active_support/deprecation'
+require 'active_support/core_ext/string/filters'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -58,13 +59,11 @@ module ActiveRecord
# * +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 close dead connections, which can
- # occur if a programmer forgets to close a connection at the end of a
- # thread or a thread dies unexpectedly. (Default nil, which means don't
- # run the Reaper).
- # * +dead_connection_timeout+: number of seconds from last checkout
- # after which the Reaper will consider a connection reapable. (default
- # 5 seconds).
+ # 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).
class ConnectionPool
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
# with which it shares a Monitor. But could be a generic Queue.
@@ -86,7 +85,7 @@ module ActiveRecord
end
end
- # Return the number of threads currently waiting on this
+ # Returns the number of threads currently waiting on this
# queue.
def num_waiting
synchronize do
@@ -222,7 +221,7 @@ module ActiveRecord
include MonitorMixin
- attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout
+ attr_accessor :automatic_reconnect, :checkout_timeout
attr_reader :spec, :connections, :size, :reaper
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
@@ -236,38 +235,31 @@ module ActiveRecord
@spec = spec
- # The cache of reserved connections mapped to threads
- @reserved_connections = {}
-
- @checkout_timeout = spec.config[:checkout_timeout] || 5
- @dead_connection_timeout = spec.config[:dead_connection_timeout]
+ @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
@reaper = Reaper.new self, spec.config[:reaping_frequency]
@reaper.run
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
+ # The cache of reserved connections mapped to threads
+ @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
+
@connections = []
@automatic_reconnect = true
@available = Queue.new self
end
- # Hack for tests to be able to add connections. Do not call outside of tests
- def insert_connection_for_test!(c) #:nodoc:
- synchronize do
- @connections << c
- @available.add c
- end
- end
-
# Retrieve the connection associated with the current thread, or call
# #checkout to obtain one if necessary.
#
# #connection can be called any number of times; the connection is
# held in a hash keyed by the thread id.
def connection
- synchronize do
+ # this is correctly done double-checked locking
+ # (ThreadSafe::Cache's lookups have volatile semantics)
+ @reserved_connections[current_connection_id] || synchronize do
@reserved_connections[current_connection_id] ||= checkout
end
end
@@ -310,7 +302,7 @@ module ActiveRecord
# Disconnects all connections in the pool, and clears the pool.
def disconnect!
synchronize do
- @reserved_connections = {}
+ @reserved_connections.clear
@connections.each do |conn|
checkin conn
conn.disconnect!
@@ -323,14 +315,12 @@ module ActiveRecord
# Clears the cache which maps classes.
def clear_reloadable_connections!
synchronize do
- @reserved_connections = {}
+ @reserved_connections.clear
@connections.each do |conn|
checkin conn
conn.disconnect! if conn.requires_reloading?
end
- @connections.delete_if do |conn|
- conn.requires_reloading?
- end
+ @connections.delete_if(&:requires_reloading?)
@available.clear
@connections.each do |conn|
@available.add conn
@@ -338,11 +328,6 @@ module ActiveRecord
end
end
- def clear_stale_cached_connections! # :nodoc:
- reap
- end
- deprecate :clear_stale_cached_connections! => "Please use #reap instead"
-
# Check-out a database connection from the pool, indicating that you want
# to use it. You should call #checkin when you no longer need this.
#
@@ -372,11 +357,13 @@ module ActiveRecord
# calling +checkout+ on this pool.
def checkin(conn)
synchronize do
- conn.run_callbacks :checkin do
+ owner = conn.owner
+
+ conn._run_checkin_callbacks do
conn.expire
end
- release conn
+ release owner
@available.add conn
end
@@ -389,22 +376,30 @@ module ActiveRecord
@connections.delete conn
@available.delete conn
- # FIXME: we might want to store the key on the connection so that removing
- # from the reserved hash will be a little easier.
- release conn
+ release conn.owner
@available.add checkout_new_connection if @available.any_waiting?
end
end
- # Removes dead connections from the pool. A dead connection can occur
- # if a programmer forgets to close a connection at the end of a thread
+ # Recover lost connections for the pool. A lost connection can occur if
+ # a programmer forgets to checkin a connection at the end of a thread
# or a thread dies unexpectedly.
def reap
- synchronize do
- stale = Time.now - @dead_connection_timeout
- connections.dup.each do |conn|
- remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
+ stale_connections = synchronize do
+ @connections.select do |conn|
+ conn.in_use? && !conn.owner.alive?
+ end
+ end
+
+ stale_connections.each do |conn|
+ synchronize do
+ if conn.active?
+ conn.reset!
+ checkin conn
+ else
+ remove conn
+ end
end
end
end
@@ -424,20 +419,15 @@ module ActiveRecord
elsif @connections.size < @size
checkout_new_connection
else
+ reap
@available.poll(@checkout_timeout)
end
end
- def release(conn)
- thread_id = if @reserved_connections[current_connection_id] == conn
- current_connection_id
- else
- @reserved_connections.keys.find { |k|
- @reserved_connections[k] == conn
- }
- end
+ def release(owner)
+ thread_id = owner.object_id
- @reserved_connections.delete thread_id if thread_id
+ @reserved_connections.delete thread_id
end
def new_connection
@@ -458,7 +448,7 @@ module ActiveRecord
end
def checkout_and_verify(c)
- c.run_callbacks :checkout do
+ c._run_checkout_callbacks do
c.verify!
end
c
@@ -471,30 +461,55 @@ module ActiveRecord
#
# For example, suppose that you have 5 models, with the following hierarchy:
#
- # |
- # +-- Book
- # | |
- # | +-- ScaryBook
- # | +-- GoodBook
- # +-- Author
- # +-- BankAccount
+ # class Author < ActiveRecord::Base
+ # end
+ #
+ # class BankAccount < ActiveRecord::Base
+ # end
+ #
+ # class Book < ActiveRecord::Base
+ # establish_connection "library_db"
+ # end
+ #
+ # class ScaryBook < Book
+ # end
#
- # Suppose that Book is to connect to a separate database (i.e. one other
- # than the default database). Then Book, ScaryBook and GoodBook will all use
- # the same connection pool. Likewise, Author and BankAccount will use the
- # same connection pool. However, the connection pool used by Author/BankAccount
- # is not the same as the one used by Book/ScaryBook/GoodBook.
+ # class GoodBook < Book
+ # end
#
- # Normally there is only a single ConnectionHandler instance, accessible via
- # ActiveRecord::Base.connection_handler. Active Record models use this to
- # determine the connection pool that they should use.
+ # And a database.yml that looked like this:
+ #
+ # development:
+ # database: my_application
+ # host: localhost
+ #
+ # library_db:
+ # database: library
+ # host: some.library.org
+ #
+ # Your primary database in the development environment is "my_application"
+ # but the Book model connects to a separate database called "library_db"
+ # (this can even be a database on a different machine).
+ #
+ # Book, ScaryBook and GoodBook will all use the same connection pool to
+ # "library_db" while Author, BankAccount, and any other models you create
+ # will use the default connection pool to "my_application".
+ #
+ # The various connection pools are managed by a single instance of
+ # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
+ # All Active Record models use this handler to determine the connection pool that they
+ # should use.
class ConnectionHandler
def initialize
- # These hashes are keyed by klass.name, NOT klass. Keying them by klass
+ # These caches are keyed by klass.name, NOT klass. Keying them by klass
# alone would lead to memory leaks in development mode as all previous
# instances of the class would stay in memory.
- @owner_to_pool = Hash.new { |h,k| h[k] = {} }
- @class_to_pool = Hash.new { |h,k| h[k] = {} }
+ @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
+ h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
+ end
+ @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
+ h[k] = ThreadSafe::Cache.new
+ end
end
def connection_pool_list
@@ -502,15 +517,17 @@ module ActiveRecord
end
def connection_pools
- ActiveSupport::Deprecation.warn(
- "In the next release, this will return the same as #connection_pool_list. " \
- "(An array of pools, rather than a hash mapping specs to pools.)"
- )
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ In the next release, this will return the same as `#connection_pool_list`.
+ (An array of pools, rather than a hash mapping specs to pools.)
+ MSG
+
Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
end
def establish_connection(owner, spec)
@class_to_pool.clear
+ raise RuntimeError, "Anonymous class is not allowed." unless owner.name
owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
end
@@ -542,7 +559,10 @@ module ActiveRecord
# for (not necessarily the current class).
def retrieve_connection(klass) #:nodoc:
pool = retrieve_connection_pool(klass)
- (pool && pool.connection) or raise ConnectionNotEstablished
+ raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
+ conn = pool.connection
+ raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
+ conn
end
# Returns true if a connection that's accessible to this class has
@@ -570,10 +590,10 @@ module ActiveRecord
# When a connection is established or removed, we invalidate the cache.
#
# Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
- # However, benchmarking (https://gist.github.com/3552829) showed that #fetch is
- # significantly slower than #[]. So in the nil case, no caching will take place,
- # but that's ok since the nil case is not the common one that we wish to optimise
- # for.
+ # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
+ # #fetch is significantly slower than #[]. So in the nil case, no caching will
+ # take place, but that's ok since the nil case is not the common one that we wish
+ # to optimise for.
def retrieve_connection_pool(klass)
class_to_pool[klass.name] ||= begin
until pool = pool_for(klass)
@@ -620,7 +640,7 @@ module ActiveRecord
end
def call(env)
- testing = env.key?('rack.test')
+ testing = env['rack.test']
response = @app.call(env)
response[2] = ::Rack::BodyProxy.new(response[2]) do
@@ -628,7 +648,7 @@ module ActiveRecord
end
response
- rescue
+ rescue Exception
ActiveRecord::Base.clear_active_connections! unless testing
raise
end