Class: ActiveRecord::ConnectionAdapters::ConnectionHandler

Inherits:
Object
  • Object
show all
Defined in:
activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb

Overview

ConnectionHandler is a collection of ConnectionPool objects. It is used for keeping separate connection pools that connect to different databases.

For example, suppose that you have 5 models, with the following hierarchy:

class Author < ActiveRecord::Base
end

class BankAccount < ActiveRecord::Base
end

class Book < ActiveRecord::Base
  establish_connection :library_db
end

class ScaryBook < Book
end

class GoodBook < Book
end

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.

The ConnectionHandler class is not coupled with the Active models, as it has no knowledge about the model. The model needs to pass a connection specification name to the handler, in order to look up the correct connection pool.

Defined Under Namespace

Classes: StringConnectionOwner

Instance Method Summary collapse

Constructor Details

#initializeConnectionHandler

Returns a new instance of ConnectionHandler.


75
76
77
78
79
80
81
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 75

def initialize
  # These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name).
  @owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)

  # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
  ObjectSpace.define_finalizer self, FINALIZER
end

Instance Method Details

#active_connections?(role = ActiveRecord::Base.current_role) ⇒ Boolean

Returns true if there are any active connections among the connection pools that the ConnectionHandler is managing.

Returns:

  • (Boolean)

135
136
137
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 135

def active_connections?(role = ActiveRecord::Base.current_role)
  connection_pool_list(role).any?(&:active_connection?)
end

#all_connection_poolsObject


95
96
97
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 95

def all_connection_pools
  owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
end

#clear_active_connections!(role = ActiveRecord::Base.current_role) ⇒ Object

Returns any connections in use by the current thread back to the pool, and also returns connections to the pool cached by threads that are no longer alive.


142
143
144
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 142

def clear_active_connections!(role = ActiveRecord::Base.current_role)
  connection_pool_list(role).each(&:release_connection)
end

#clear_all_connections!(role = ActiveRecord::Base.current_role) ⇒ Object


153
154
155
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 153

def clear_all_connections!(role = ActiveRecord::Base.current_role)
  connection_pool_list(role).each(&:disconnect!)
end

#clear_reloadable_connections!(role = ActiveRecord::Base.current_role) ⇒ Object

Clears the cache which maps classes.

See ConnectionPool#clear_reloadable_connections! for details.


149
150
151
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 149

def clear_reloadable_connections!(role = ActiveRecord::Base.current_role)
  connection_pool_list(role).each(&:clear_reloadable_connections!)
end

#connected?(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) ⇒ Boolean

Returns true if a connection that's accessible to this class has already been opened.

Returns:

  • (Boolean)

190
191
192
193
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 190

def connected?(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
  pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
  pool && pool.connected?
end

#connection_pool_list(role = ActiveRecord::Base.current_role) ⇒ Object Also known as: connection_pools


99
100
101
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 99

def connection_pool_list(role = ActiveRecord::Base.current_role)
  owner_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
end

#connection_pool_namesObject

:nodoc:


91
92
93
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 91

def connection_pool_names # :nodoc:
  owner_to_pool_manager.keys
end

#establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard) ⇒ Object


104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 104

def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
  owner_name = StringConnectionOwner.new(config.to_s) if config.is_a?(Symbol)

  pool_config = resolve_pool_config(config, owner_name, role, shard)
  db_config = pool_config.db_config

  # Protects the connection named `ActiveRecord::Base` from being removed
  # if the user calls `establish_connection :primary`.
  if owner_to_pool_manager.key?(pool_config.connection_specification_name)
    remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard)
  end

  message_bus = ActiveSupport::Notifications.instrumenter
  payload = {}
  if pool_config
    payload[:spec_name] = pool_config.connection_specification_name
    payload[:shard] = shard
    payload[:config] = db_config.configuration_hash
  end

  owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
  pool_manager = get_pool_manager(pool_config.connection_specification_name)
  pool_manager.set_pool_config(role, shard, pool_config)

  message_bus.instrument("!connection.active_record", payload) do
    pool_config.pool
  end
end

#flush_idle_connections!(role = ActiveRecord::Base.current_role) ⇒ Object

Disconnects all currently idle connections.

See ConnectionPool#flush! for details.


160
161
162
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 160

def flush_idle_connections!(role = ActiveRecord::Base.current_role)
  connection_pool_list(role).each(&:flush!)
end

#prevent_writesObject

:nodoc:


83
84
85
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 83

def prevent_writes # :nodoc:
  ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes]
end

#prevent_writes=(prevent_writes) ⇒ Object

:nodoc:


87
88
89
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 87

def prevent_writes=(prevent_writes) # :nodoc:
  ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes
end

#remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) ⇒ Object


195
196
197
198
199
200
201
202
203
204
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 195

def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
  if pool_manager = get_pool_manager(owner)
    pool_config = pool_manager.remove_pool_config(role, shard)

    if pool_config
      pool_config.disconnect!
      pool_config.db_config
    end
  end
end

#retrieve_connection(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) ⇒ Object

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 for (not necessarily the current class).


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 168

def retrieve_connection(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
  pool = retrieve_connection_pool(spec_name, role: role, shard: shard)

  unless pool
    if shard != ActiveRecord::Base.default_shard
      message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard."
    elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
      message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
    elsif role != ActiveRecord::Base.default_role
      message = "No connection pool for '#{spec_name}' found for the '#{role}' role."
    else
      message = "No connection pool for '#{spec_name}' found."
    end

    raise ConnectionNotEstablished, message
  end

  pool.connection
end

#retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) ⇒ Object

Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager. This makes retrieving the connection pool O(1) once the process is warm. When a connection is established or removed, we invalidate the cache.


209
210
211
212
# File 'activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb', line 209

def retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
  pool_config = get_pool_manager(owner)&.get_pool_config(role, shard)
  pool_config&.pool
end