Архитектура PDO Pool

Эта статья описывает внутреннее устройство PDO Pool. Если вы ищете руководство по использованию, см. PDO Pool: пул соединений.

Два уровня архитектуры

PDO Pool состоит из двух слоёв:

1. Ядро PDO (pdo_pool.c) — логика привязки соединений к корутинам, управление транзакциями, подсчёт ссылок от стейтментов.

2. Async Pool (zend_async_pool_t) — универсальный пул ресурсов из async-расширения. Управляет очередью свободных соединений, лимитами, healthcheck-ами. Ничего не знает о PDO — работает с абстрактными zval.

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

Диаграмма компонентов

PDO Pool — Компоненты

Template-соединение

При создании PDO с пулом ядро не открывает реальное TCP-соединение. Вместо этого создаётся template — объект pdo_dbh_t, который хранит DSN, логин, пароль и ссылку на драйвер. Все реальные соединения создаются позже, по мере необходимости, на основе этого шаблона.

Для template вызывается db_handle_init_methods() вместо db_handle_factory(). Этот метод устанавливает таблицу методов драйвера (dbh->methods), но не создаёт TCP-соединение и не аллоцирует driver_data.

Жизненный цикл соединения

Жизненный цикл соединения в пуле

Создание соединения из пула (sequence)

Создание соединения из пула

Internal API

pdo_pool.c — публичные функции

Функция Назначение
pdo_pool_create() Создаёт пул для pdo_dbh_t на основе атрибутов конструктора
pdo_pool_destroy() Освобождает все соединения, закрывает пул, очищает хэш-таблицу
pdo_pool_acquire_conn() Возвращает соединение для текущей корутины (reuse или acquire)
pdo_pool_peek_conn() Возвращает привязанное соединение без acquire (NULL если нет)
pdo_pool_maybe_release() Возвращает соединение в пул, если нет транзакции и стейтментов
pdo_pool_get_wrapper() Возвращает PHP-объект Async\Pool для метода getPool()

pdo_pool.c — внутренние callbacks

Callback Когда вызывается
pdo_pool_factory() Пулу нужно новое соединение (acquire при пустом пуле)
pdo_pool_destructor() Пул уничтожает соединение (при close или eviction)
pdo_pool_healthcheck() Периодическая проверка — соединение ещё живое?
pdo_pool_before_release() Перед возвратом в пул — rollback незавершённых транзакций
pdo_pool_free_conn() Закрывает драйверное соединение, освобождает память

Привязка к корутине

Соединения привязываются к корутинам через хэш-таблицу pool_connections, где ключ — идентификатор корутины, а значение — указатель на pdo_dbh_t.

Идентификатор корутины вычисляется функцией pdo_pool_coro_key():

Cleanup при завершении корутины

При привязке соединения к корутине регистрируется callback pdo_pool_cleanup_callback через coro->event.add_callback(). Когда корутина завершается (нормально или с ошибкой), callback автоматически возвращает соединение в пул. Это гарантирует отсутствие утечек соединений даже при необработанных исключениях.

Pinning: закрепление соединения

Соединение закреплено за корутиной и не вернётся в пул, если выполняется хотя бы одно условие:

Refcount увеличивается при создании стейтмента и уменьшается при его уничтожении. Когда оба условия сняты, pdo_pool_maybe_release() возвращает соединение в пул.

Управление credentials в factory

При создании нового соединения pdo_pool_factory() копирует строки DSN, username и password из template через estrdup(). Это необходимо, потому что драйверы могут мутировать эти поля во время db_handle_factory():

После успешного вызова db_handle_factory() копии освобождаются через efree(). При ошибке освобождение происходит через pdo_pool_free_conn(), которая также используется деструктором пула.

Несовместимость с persistent connections

Persistent-соединения (PDO::ATTR_PERSISTENT) несовместимы с пулом. Persistent-соединение привязано к процессу и переживает запрос, а пул создаёт соединения на уровне запроса с автоматическим управлением жизненным циклом. Попытка включить оба атрибута одновременно приведёт к ошибке.

Дальше что?