From 3cfea287baa596833bd5e8bda5e12fd03250008f Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 27 Feb 2006 21:37:30 +0000 Subject: Speed up class -> connection caching and stale connection verification. Closes #3979. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3693 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- .../abstract/connection_specification.rb | 111 +++++++++++++++------ .../connection_adapters/abstract_adapter.rb | 15 ++- 2 files changed, 95 insertions(+), 31 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index a73ce02104..2d24dee879 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -7,24 +7,70 @@ module ActiveRecord end end + # Check connections for active? after +@@connection_cache_timeout+ seconds + # defaults to 5 minutes + cattr_accessor :connection_cache_timeout + @@connection_cache_timeout = 300 + # The class -> [adapter_method, config] map @@defined_connections = {} - # The class -> thread id -> adapter cache. - @@connection_cache = Hash.new { |h, k| h[k] = Hash.new } + # The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency) + @@connection_cache = {} + # retrieve the connection cache + def self.connection_cache + if @@allow_concurrency + @@connection_cache[Thread.current.object_id] ||= {} + else + @@connection_cache + end + end + + @connection_cache_key = nil + def self.connection_cache_key + @connection_cache_key ||= + if active_connections[name] || @@defined_connections[name] + name + elsif self == ActiveRecord::Base + nil + else + superclass.connection_cache_key + end + end + + def self.clear_connection_cache_key + @connection_cache_key = nil + subclasses.each{|klass| klass.clear_connection_cache_key } + end + # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work unrelated # to any of the specific Active Records. def self.connection - @@connection_cache[Thread.current.object_id][name] ||= retrieve_connection + if (cache_key = @connection_cache_key) && (conn = connection_cache[cache_key]) + conn + else + conn = retrieve_connection # this will set @connection_cache_key + connection_cache[@connection_cache_key] = conn + end end # Clears the cache which maps classes to connections. def self.clear_connection_cache! - @@connection_cache.clear + if @@allow_concurrency + @@connection_cache.delete(Thread.current.object_id) + else + @@connection_cache = {} + end end - + + # Verify connection cache. + def self.verify_connection_cache! + timeout = @@connection_cache_timeout + connection_cache.each_value { |connection| connection.verify!(timeout) } + end + # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work that isn't # easily done without going straight to SQL. @@ -65,6 +111,8 @@ module ActiveRecord raise AdapterNotSpecified unless defined? RAILS_ENV establish_connection(RAILS_ENV) when ConnectionSpecification + clear_connection_cache_key + @connection_cache_key = name @@defined_connections[name] = spec when Symbol, String if configuration = configurations[spec.to_s] @@ -83,7 +131,7 @@ module ActiveRecord end def self.active_connections #:nodoc: - if allow_concurrency + if @@allow_concurrency Thread.current['active_connections'] ||= {} else @@active_connections ||= {} @@ -95,34 +143,27 @@ module ActiveRecord # opened and set as the active connection for the class it was defined # for (not necessarily the current class). def self.retrieve_connection #:nodoc: - klass = self - ar_super = ActiveRecord::Base.superclass - until klass == ar_super - if conn = active_connections[klass.name] - # Reconnect if the connection is inactive. - conn.reconnect! unless conn.active? - return conn - elsif conn = @@defined_connections[klass.name] - # Activate this connection specification. - klass.connection = conn - return self.connection - end - klass = klass.superclass + cache_key = connection_cache_key + # cache_key is nil if establish_connection hasn't been called for + # some class along the inheritance chain up to AR::Base yet + raise ConnectionNotEstablished unless cache_key + if conn = active_connections[cache_key] + # Verify the connection. + conn.verify!(@@connection_cache_timeout) + return conn + elsif conn = @@defined_connections[cache_key] + # Activate this connection specification. + klass = cache_key.constantize + klass.connection = conn + return active_connections[cache_key] + else + raise ConnectionNotEstablished end - raise ConnectionNotEstablished end # Returns true if a connection that's accessible to this class have already been opened. def self.connected? - klass = self - until klass == ActiveRecord::Base.superclass - if active_connections[klass.name] - return true - else - klass = klass.superclass - end - end - return false + active_connections[connection_cache_key] ? true : false end # Remove the connection for this class. This will close the active @@ -133,7 +174,7 @@ module ActiveRecord spec = @@defined_connections[klass.name] konn = active_connections[klass.name] @@defined_connections.delete_if { |key, value| value == spec } - @@connection_cache[Thread.current.object_id].delete_if { |key, value| value == konn } + connection_cache.delete_if { |key, value| value == konn } active_connections.delete_if { |key, value| value == konn } konn.disconnect! if konn spec.config if spec @@ -151,5 +192,15 @@ module ActiveRecord establish_connection spec end end + + # connection state logging + def self.log_connections + if logger + logger.info "Defined connections: #{@@defined_connections.inspect}" + logger.info "Active connections: #{active_connections.inspect}" + logger.info "Connection cache: #{connection_cache.inspect}" + logger.info "Connection cache key: #{@connection_cache_key}" + end + end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 8187112b64..a52d7f4fb0 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -21,10 +21,11 @@ module ActiveRecord class AbstractAdapter include Quoting, DatabaseStatements, SchemaStatements @@row_even = true - + def initialize(connection, logger = nil) #:nodoc: @connection, @logger = connection, logger @runtime = 0 + @last_verification = 0 end # Returns the human-readable name of the adapter. Use mixed case - one @@ -76,6 +77,15 @@ module ActiveRecord @active = false end + # Lazily verify this connection, calling +active?+ only if it hasn't + # been called for +timeout+ seconds. + def verify!(timeout) + now = Time.now.to_i + if (now - @last_verification) > timeout + reconnect! unless active? + @last_verification = now + end + end protected def log(sql, name) @@ -95,6 +105,9 @@ module ActiveRecord end rescue Exception => e # Log message and raise exception. + # Set last_verfication to 0, so that connection gets verified + # upon reentering the request loop + @last_verification = 0 message = "#{e.class.name}: #{e.message}: #{sql}" log_info(message, name, 0) raise ActiveRecord::StatementInvalid, message -- cgit v1.2.3