Spec-Zone.ru › Ruby on Rails 7.2

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

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

Пул подключений Active Record

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

Введение

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

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

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

  1. Просто используйте ActiveRecord::Base.lease_connection. Когда вы закончили с подключением(ями) и хотите вернуть его в пул, вызывайте ActiveRecord::Base.connection_handler.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).

Пока поток имеет подключение, выданное из пула одним из вышеперечисленных трёх методов, это подключение автоматически будет использоваться ActiveRecord запросами, выполняемыми в этом потоке. Не требуется явно передавать полученное подключение в модели или запросы Rails, например.

Параметры

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

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

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

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

Атрибуты

async_executor[R]
automatic_reconnect[RW]
checkout_timeout[RW]
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 202
def initialize(pool_config)
  super()

  @pool_config = pool_config
  @db_config = pool_config.db_config
  @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>@leases</tt> does not require
  # synchronization.
  @leases = LeaseRegistry.new

  @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
  @pinned_connection = nil
  @pinned_connections_depth = 0

  @async_executor = build_async_executor

  @schema_cache = nil

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

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

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

Вызывает метод суперкласса

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

active_connection?() Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 353
def active_connection?
  connection_lease.connection
end

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

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

checkin(conn) Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 547
def checkin(conn)
  return if @pinned_connection.equal?(conn)

  conn.lock.synchronize do
    synchronize do
      connection_lease.clear(conn)

      conn._run_checkin_callbacks do
        conn.expire
      end

      @available.add conn
    end
  end
end

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

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

checkout(checkout_timeout = @checkout_timeout) Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 524
def checkout(checkout_timeout = @checkout_timeout)
  if @pinned_connection
    @pinned_connection.lock.synchronize do
      synchronize do
        @pinned_connection.verify!
        # Any leased connection must be in @connections otherwise
        # some methods like #connected? won't behave correctly
        unless @connections.include?(@pinned_connection)
          @connections << @pinned_connection
        end
      end
    end
    @pinned_connection
  else
    checkout_and_verify(acquire_connection(checkout_timeout))
  end
end

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

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

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

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

Возбуждает:

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

clear_reloadable_connections(raise_on_acquisition_timeout = true) Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 482
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!() Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 506
def clear_reloadable_connections!
  clear_reloadable_connections(false)
end

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

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

connected?() Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 404
def connected?
  synchronize { @connections.any?(&:connected?) }
end

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

connection() Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 295
      def connection
        ActiveRecord.deprecator.warn(<<~MSG)
          ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
          and will be removed in Rails 8.0. Use #lease_connection instead.
        MSG
        lease_connection
      end
connections() Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 419
def connections
  synchronize { @connections.dup }
end

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

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

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

disconnect(raise_on_acquisition_timeout = true) Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 429
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 = []
      @leases.clear
      @available.clear
    end
  end
end

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

Возбуждает:

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

disconnect!() Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 452
def disconnect!
  disconnect(false)
end

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

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

flush(minimum_idle = @idle_timeout) Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 620
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!() Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 642
def flush!
  reap
  flush(-1)
end

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

lease_connection() Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 285
def lease_connection
  lease = connection_lease
  lease.sticky = true
  lease.connection ||= checkout
end

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

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

reap() Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 597
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(existing_lease = nil) Show source
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 365
def release_connection(existing_lease = nil)
  if conn = connection_lease.release
    checkin conn
    return true
  end
  false
end

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

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

remove(conn) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 565
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

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

schema_cache() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 255
def schema_cache
  @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
end
schema_reflection=(schema_reflection) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 259
def schema_reflection=(schema_reflection)
  pool_config.schema_reflection = schema_reflection
  @schema_cache = nil
end
stat() Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 654
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(prevent_permanent_checkout: false) { |connection| ... } Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 382
def with_connection(prevent_permanent_checkout: false)
  lease = connection_lease
  sticky_was = lease.sticky
  lease.sticky = false if prevent_permanent_checkout

  if lease.connection
    begin
      yield lease.connection
    ensure
      lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
    end
  else
    begin
      yield lease.connection = checkout
    ensure
      lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
      release_connection(lease) unless lease.sticky
    end
  end
end

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

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

Spec-Zone.ru

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