Spec-Zone.ru › Ruby on Rails 8.1

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

Родительский класс:
Object
Подключённые модули:

Пул соединений 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.

Параметры

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

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

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

  • keepalive: интервал в секундах между проверками поддержания активности простаивающего соединения (по умолчанию — 600 секунд).

  • max_age: время в секундах, в течение которого пул будет разрешать существование соединения, прежде чем пометить его для удаления при следующем возврате в пул (по умолчанию — Float::INFINITY).

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

  • min_connections: минимальное количество соединений, которые пул будет открывать и поддерживать (по умолчанию — 0).

  • pool_jitter: максимальный коэффициент уменьшения для интервалов max_age и keepalive (по умолчанию — 0.2; диапазон — 0.0–1.0).

Константы

WeakThreadKeyMap

Атрибуты

async_executor [R]
automatic_reconnect [RW]
checkout_timeout [RW]
db_config [R]
keepalive [R]
max_age [R]
max_connections [R]
min_connections [R]
pool_config [R]
reaper [R]
role [R]
shard [R]
size [R]

Открытые методы класса

install_executor_hooks (executor = ActiveSupport::Executor) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 231
def install_executor_hooks(executor = ActiveSupport::Executor)
  executor.register_hook(ExecutorHooks)
end
new (pool_config) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 251
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
  @max_connections = db_config.max_connections
  @min_connections = db_config.min_connections
  @max_age = db_config.max_age
  @keepalive = db_config.keepalive

  # 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

  # Sometimes otherwise-idle connections are temporarily held by the Reaper for
  # maintenance. This variable tracks the number of connections currently in that
  # state -- if a thread requests a connection and there are none available, it
  # will await any in-maintenance connections in preference to creating a new one.
  @maintaining = 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

  @activated = false
  @original_context = ActiveSupport::IsolatedExecutionState.context

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

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

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

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

Открытые методы экземпляра

activate () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 342
def activate
  @activated = true
end
activated? () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 346
def activated?
  @activated
end
active_connection? () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 419
def active_connection?
  connection_lease.connection
end

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

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

checkin (conn) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 658
def checkin(conn)
  return if @pinned_connection.equal?(conn)

  conn.lock.synchronize do
    synchronize do
      connection_lease.clear(conn)
      conn.expire
      @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 630
def checkout(checkout_timeout = @checkout_timeout)
  return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection

  @pinned_connection.lock.synchronize do
    synchronize do
      # The pinned connection may have been cleaned up before we synchronized, so check if it is still present
      if @pinned_connection
        @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

        @pinned_connection
      else
        checkout_and_verify(acquire_connection(checkout_timeout))
      end
    end
  end
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 588
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 612
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 490
def connected?
  synchronize { @connections.any?(&:connected?) }
end

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

connections () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 505
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 515
def disconnect(raise_on_acquisition_timeout = true)
  @reaper_lock.synchronize do
    return if self.discarded?

    with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
      synchronize do
        return if self.discarded?
        @connections.each do |conn|
          if conn.in_use?
            conn.steal!
            checkin conn
          end
          conn.disconnect!
        end
        @connections = []
        @leases.clear
        @available.clear

        # Stop maintaining the minimum size until reactivated
        @activated = false
      end
    end
  end
end

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

Вызывает исключение:

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

disconnect! () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 546
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 727
def flush(minimum_idle = @idle_timeout)
  return if minimum_idle.nil?

  removed_connections = synchronize do
    return if self.discarded?

    idle_connections = @connections.select do |conn|
      !conn.in_use? && conn.seconds_idle >= minimum_idle
    end.sort_by { |conn| -conn.seconds_idle } # sort longest idle first

    # Don't go below our configured pool minimum unless we're flushing
    # everything
    idles_to_retain =
      if minimum_idle > 0
        @min_connections - (@connections.size - idle_connections.size)
      else
        0
      end

    if idles_to_retain > 0
      idle_connections.pop idles_to_retain
    end

    idle_connections.each do |conn|
      conn.lease

      @available.delete conn
      @connections.delete conn
    end
  end

  removed_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 766
def flush!
  reap
  flush(-1)

  # Stop maintaining the minimum size until reactivated
  @activated = false
end

Отключает все простаивающие соединения. Соединения, полученные из пула в данный момент, не затрагиваются. Пул перестанет поддерживать минимальный размер, пока не будет активирован повторно (например, при последующем получении соединения).

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

  sequential_maintenance -> c { (c.seconds_since_last_activity || 0) > c.pool_jitter(threshold) } do |conn|
    # conn.active? will cause some amount of network activity, which is all
    # we need to provide a keepalive signal.
    #
    # If it returns false, the connection is already broken; disconnect,
    # so it can be found and repaired.
    conn.disconnect! unless conn.active?
  end
end

Проверяет соединения, простаивающие дольше настроенного интервала поддержания активности. Заодно это подтверждает, что соединение всё ещё работает, но главная цель — сообщить серверу (и всем промежуточным узлам сети), что соединение по-прежнему используется.

lease_connection () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 355
def lease_connection
  lease = connection_lease
  lease.connection ||= checkout
  lease.sticky = true
  lease.connection
end

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

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

pool_transaction_isolation_level () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 891
def pool_transaction_isolation_level
  isolation_level_key = "activerecord_pool_transaction_isolation_level_#{db_config.name}"
  ActiveSupport::IsolatedExecutionState[isolation_level_key]
end
pool_transaction_isolation_level= (isolation_level) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 896
def pool_transaction_isolation_level=(isolation_level)
  isolation_level_key = "activerecord_pool_transaction_isolation_level_#{db_config.name}"
  ActiveSupport::IsolatedExecutionState[isolation_level_key] = isolation_level
end
preconnect () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 810
def preconnect
  sequential_maintenance -> c { (!c.connected? || !c.verified?) && c.allow_preconnect } do |conn|
    conn.connect!
  rescue
    # Wholesale rescue: there's nothing we can do but move on. The
    # connection will go back to the pool, and the next consumer will
    # presumably try to connect again -- which will either work, or
    # fail and they'll be able to report the exception.
  end
end

Заранее устанавливает соединения для всего пула. Это избавляет пользователей пула от ожидания установки соединения при его первом использовании после получения из пула.

prepopulate () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 776
def prepopulate
  need_new_connections = nil

  synchronize do
    return if self.discarded?

    # We don't want to start prepopulating until we know the pool is wanted,
    # so we can avoid maintaining full pools in one-off scripts etc.
    return unless @activated

    need_new_connections = @connections.size < @min_connections
  end

  if need_new_connections
    while new_conn = try_to_checkout_new_connection { @connections.size < @min_connections }
      new_conn.allow_preconnect = true
      checkin(new_conn)
    end
  end
end

Гарантирует, что пул содержит как минимум настроенное минимальное количество соединений.

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

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

recycle! () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 841
def recycle!
  synchronize do
    return if self.discarded?

    @connections.each do |conn|
      conn.force_retirement
    end
  end

  retire_old_connections
end

Немедленно помечает все текущие соединения как подлежащие замене, как если бы они достигли значения max_age, даже если max_age не настроен.

release_connection (existing_lease = nil) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 431
def release_connection(existing_lease = nil)
  return if self.discarded?

  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 672
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 == @max_connections).
    # 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.num_waiting > @maintaining
  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 @max_connections limit).
  bulk_make_new_connections(1) if needs_new_connection
end

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

retire_old_connections (max_age = @max_age) Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 797
def retire_old_connections(max_age = @max_age)
  max_age ||= Float::INFINITY

  sequential_maintenance -> c { c.connection_age&.>= c.pool_jitter(max_age) } do |conn|
    # Disconnect, then return the adapter to the pool. Preconnect will
    # handle the rest.
    conn.disconnect!
  end
end
schema_cache () Показать исходный код
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 317
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 321
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 864
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 450
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