Spec-Zone.ru › Ruby on Rails 7.0

класс ActiveRecord::ConnectionAdapters::ConnectionPool

Родитель:
Объект
Включенные модули:
ActiveRecord::ConnectionAdapters::QueryCache::ConnectionPoolConfiguration

Базовый класс пула соединений для управления базами данных Active Record.

Введение

Пул соединений синхронизирует доступ потоков к ограниченному количеству подключений к базе данных. Основная идея заключается в том, что каждый поток запрашивает подключение к базе данных из пула, использует его и возвращает обратно в пул. ConnectionPool полностью потокобезопасен и гарантирует, что одно подключение не может использоваться двумя потоками одновременно, если соглашение ConnectionPool соблюдено. Он также обрабатывает случаи, когда количество потоков превышает количество подключений: если все подключения заняты, и поток пытается получить подключение, то ConnectionPool будет ожидать, пока другой поток вернёт подключение в пул.

Получение (выдача) подключения

Подключения могут быть получены и использованы из пула несколькими способами:

  1. Просто используйте ActiveRecord::Base.connection, как в Active Record 2.1 и более ранних версиях (до пулов подключений). Когда вы закончите работу с подключением(ями) и хотите вернуть его в пул, вызывайте ActiveRecord::Base.clear_active_connections!. Это будет стандартным поведением Active Record при использовании в сочетании с циклом обработки запросов Action Pack.

  2. Вручную выдать подключение из пула с помощью ActiveRecord::Base.connection_pool.checkout. Вы несете ответственность за возврат этого подключения в пул после завершения работы, вызвав ActiveRecord::Base.connection_pool.checkin(connection).

  3. Используйте ActiveRecord::Base.connection_pool.with_connection(&block), который получает подключение, передает его в качестве единственного аргумента в блок и возвращает его в пул после завершения блока.

Подключения в пуле фактически являются объектами AbstractAdapter (или объектами, совместимыми с интерфейсом AbstractAdapter).

Параметры

Существует несколько параметров, связанных с пулом подключений, которые можно добавить в конфигурацию подключения к базе данных:

  • pool: максимальное количество подключений, которые может управлять пул (по умолчанию 5).

  • idle_timeout: количество секунд, в течение которых подключение будет храниться неиспользуемым в пуле, прежде чем оно будет автоматически отключено (по умолчанию 300 секунд). Установите это значение в ноль, чтобы хранить подключения постоянно.

  • checkout_timeout: количество секунд ожидания подключения, прежде чем отказаться и выдать ошибку таймаута (по умолчанию 5 секунд).

Атрибуты

async_executor[R]
automatic_reconnect[RW]
checkout_timeout[RW]
connection_class[R]
connection_klass[R]
db_config[R]
pool_config[R]
reaper[R]
role[R]
shard[R]
size[R]

Публичные методы класса

new(pool_config) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 120
def initialize(pool_config)
  super()

  @pool_config = pool_config
  @db_config = pool_config.db_config
  @connection_class = pool_config.connection_class
  @role = pool_config.role
  @shard = pool_config.shard

  @checkout_timeout = db_config.checkout_timeout
  @idle_timeout = db_config.idle_timeout
  @size = db_config.pool

  # This variable tracks the cache of threads mapped to reserved connections, with the
  # sole purpose of speeding up the +connection+ method. It is not the authoritative
  # registry of which thread owns which connection. Connection ownership is tracked by
  # the +connection.owner+ attr on each +connection+ instance.
  # The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
  # then that +thread+ does indeed own that +conn+. However, an absence of such
  # mapping does not mean that the +thread+ doesn't own the said connection. In
  # that case +conn.owner+ attr should be consulted.
  # Access and modification of <tt>@thread_cached_conns</tt> does not require
  # synchronization.
  @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)

  @connections         = []
  @automatic_reconnect = true

  # Connection pool allows for concurrent (outside the main +synchronize+ section)
  # establishment of new connections. This variable tracks the number of threads
  # currently in the process of independently establishing connections to the DB.
  @now_connecting = 0

  @threads_blocking_new_connections = 0

  @available = ConnectionLeasingQueue.new self

  @lock_thread = false

  @async_executor = build_async_executor

  lazily_set_schema_cache

  @reaper = Reaper.new(self, db_config.reaping_frequency)
  @reaper.run
end

Создаёт новый объект ConnectionPool. pool_config — объект PoolConfig, описывающий информацию о подключении к базе данных (например, адаптер, имя хоста, имя пользователя, пароль и т.д.), а также максимальный размер для этого ConnectionPool.

По умолчанию максимальный размер ConnectionPool — 5.

Вызывает метод суперкласса ActiveRecord::ConnectionAdapters::QueryCache::ConnectionPoolConfiguration::new

Публичные методы экземпляра

active_connection?() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 189
def active_connection?
  @thread_cached_conns[connection_cache_key(current_thread)]
end

Возвращает true, если есть открытое соединение, используемое для текущего потока.

Этот метод работает только для соединений, полученных через connection или with_connection методы. Соединения, полученные через checkout, не будут обнаружены active_connection?

checkin(conn) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 349
def checkin(conn)
  conn.lock.synchronize do
    synchronize do
      remove_connection_from_thread_cache conn

      conn._run_checkin_callbacks do
        conn.expire
      end

      @available.add conn
    end
  end
end

Возвращает соединение с базой данных в пул, указывая, что оно больше не нужно.

conn: объект AbstractAdapter, который был получен ранее вызовом checkout в этом пуле.

checkout(checkout_timeout = @checkout_timeout) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 340
def checkout(checkout_timeout = @checkout_timeout)
  checkout_and_verify(acquire_connection(checkout_timeout))
end

Извлекает соединение с базой данных из пула, указывая, что вы хотите его использовать. Вы должны вызвать checkin, когда оно больше не понадобится.

Это делается либо путем возврата и аренды существующего соединения, либо путем создания нового соединения и его аренды.

Если все соединения арендованы, и пул заполнен (то есть количество текущих арендованных соединений больше или равно пределу размера), будет вызвано исключение ActiveRecord::ConnectionTimeoutError.

Возвращает: объект AbstractAdapter.

Вызывает:

  • ActiveRecord::ConnectionTimeoutError невозможно получить соединение из пула.

clear_reloadable_connections(raise_on_acquisition_timeout = true) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 298
def clear_reloadable_connections(raise_on_acquisition_timeout = true)
  with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
    synchronize do
      @connections.each do |conn|
        if conn.in_use?
          conn.steal!
          checkin conn
        end
        conn.disconnect! if conn.requires_reloading?
      end
      @connections.delete_if(&:requires_reloading?)
      @available.clear
    end
  end
end

Очищает кэш, который отображает классы, и переподключает соединения, требующие перезагрузки.

Вызывает:

  • ActiveRecord::ExclusiveConnectionTimeoutError если невозможно получить право владения всеми соединениями в пуле в течение интервала времени ожидания (время по умолчанию — spec.db_config.checkout_timeout * 2 секунд).

clear_reloadable_connections!() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 322
def clear_reloadable_connections!
  clear_reloadable_connections(false)
end

Очищает кэш, который отображает классы, и переподключает соединения, требующие перезагрузки.

Пул сначала пытается получить право владения всеми соединениями. Если это невозможно сделать в течение интервала времени ожидания (время по умолчанию — spec.db_config.checkout_timeout * 2 секунд), то пул принудительно очищает кэш и перезагружает соединения, не обращая внимания на другие потоки, владеющие соединениями.

connected?() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 221
def connected?
  synchronize { @connections.any? }
end

Возвращает true, если соединение уже открыто.

connection() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 180
def connection
  @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
end

Извлекает соединение, связанное с текущим потоком, или вызывает checkout для получения соединения, если это необходимо.

connection может вызываться любое количество раз; соединение хранится в кэше, ключом которого является поток.

connections() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 236
def connections
  synchronize { @connections.dup }
end

Возвращает массив, содержащий соединения, которые в данный момент находятся в пуле. Доступ к массиву не требует синхронизации пула, поскольку массив создается заново и не сохраняется пулом.

Однако этот метод обходит механизм потокобезопасного доступа к соединениям ConnectionPool. Возвращенное соединение может принадлежать другому потоку, не иметь владельца или случайно принадлежать вызывающему потоку.

Вызов методов для соединения без права владения зависит от гарантий потокобезопасности базового метода. Многие методы в классах адаптеров соединений по своей природе не являются потокобезопасными.

disconnect(raise_on_acquisition_timeout = true) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 246
def disconnect(raise_on_acquisition_timeout = true)
  with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
    synchronize do
      @connections.each do |conn|
        if conn.in_use?
          conn.steal!
          checkin conn
        end
        conn.disconnect!
      end
      @connections = []
      @available.clear
    end
  end
end

Отключает все соединения в пуле и очищает пул.

Вызывает:

  • ActiveRecord::ExclusiveConnectionTimeoutError если невозможно получить право владения всеми соединениями в пуле в течение интервала времени ожидания (время по умолчанию — spec.db_config.checkout_timeout * 2 секунд).

disconnect!() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 268
def disconnect!
  disconnect(false)
end

Отключает все соединения в пуле и очищает пул.

Пул сначала пытается получить право владения всеми соединениями. Если это невозможно сделать в течение интервала времени ожидания (время по умолчанию — spec.db_config.checkout_timeout * 2 секунд), то пул принудительно отключается, не обращая внимания на другие потоки, владеющие соединениями.

flush(minimum_idle = @idle_timeout) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 420
def flush(minimum_idle = @idle_timeout)
  return if minimum_idle.nil?

  idle_connections = synchronize do
    return if self.discarded?
    @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

Отключает все соединения, которые были неактивны в течение как минимум minimum_idle секунд. Соединения, которые в данный момент извлечены или были возвращены менее чем minimum_idle секунд назад, не затрагиваются.

flush!() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 442
def flush!
  reap
  flush(-1)
end

Отключает все текущие неактивные соединения. Соединения, которые в данный момент извлечены, не затрагиваются.

lock_thread=(lock_thread) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 167
def lock_thread=(lock_thread)
  if lock_thread
    @lock_thread = Thread.current
  else
    @lock_thread = nil
  end
end
reap() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 397
def reap
  stale_connections = synchronize do
    return if self.discarded?
    @connections.select do |conn|
      conn.in_use? && !conn.owner.alive?
    end.each do |conn|
      conn.steal!
    end
  end

  stale_connections.each do |conn|
    if conn.active?
      conn.reset!
      checkin conn
    else
      remove conn
    end
  end
end

Восстанавливает потерянные соединения для пула. Потерянное соединение может возникнуть, если программист забывает вернуть соединение в конце потока или поток завершается неожиданно.

release_connection(owner_thread = Thread.current) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 200
def release_connection(owner_thread = Thread.current)
  if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
    checkin conn
  end
end

Сигнализирует о том, что поток завершил работу с текущим соединением. release_connection освобождает связь соединение-поток и возвращает соединение в пул.

Этот метод работает только для соединений, полученных через connection или with_connection методы, соединения, полученные через checkout, не будут автоматически освобождены.

remove(conn) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 365
def remove(conn)
  needs_new_connection = false

  synchronize do
    remove_connection_from_thread_cache conn

    @connections.delete conn
    @available.delete conn

    # @available.any_waiting? => true means that prior to removing this
    # conn, the pool was at its max size (@connections.size == @size).
    # This would mean that any threads stuck waiting in the queue wouldn't
    # know they could checkout_new_connection, so let's do it for them.
    # Because condition-wait loop is encapsulated in the Queue class
    # (that in turn is oblivious to ConnectionPool implementation), threads
    # that are "stuck" there are helpless. They have no way of creating
    # new connections and are completely reliant on us feeding available
    # connections into the Queue.
    needs_new_connection = @available.any_waiting?
  end

  # This is intentionally done outside of the synchronized section as we
  # would like not to hold the main mutex while checking out new connections.
  # Thus there is some chance that needs_new_connection information is now
  # stale, we can live with that (bulk_make_new_connections will make
  # sure not to exceed the pool's @size limit).
  bulk_make_new_connections(1) if needs_new_connection
end

Удаляет соединение из пула соединений. Соединение останется открытым и активным, но больше не будет управляться этим пулом.

stat() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 455
def stat
  synchronize do
    {
      size: size,
      connections: @connections.size,
      busy: @connections.count { |c| c.in_use? && c.owner.alive? },
      dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
      idle: @connections.count { |c| !c.in_use? },
      waiting: num_waiting_in_queue,
      checkout_timeout: checkout_timeout
    }
  end
end

Возвращает статистику использования пула соединений Пример:

ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
with_connection() { |conn| ... } Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 210
def with_connection
  unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
    conn = connection
    fresh_connection = true
  end
  yield conn
ensure
  release_connection if fresh_connection
end

Если подключение, полученное с помощью методов connection или with_connection, уже существует, передайте его в блок. Если такое подключение не существует, выберите подключение, передайте его в блок и верните подключение после завершения.

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API