Архитектура PDO Pool
Эта статья описывает внутреннее устройство PDO Pool. Если вы ищете руководство по использованию, см. PDO Pool: пул соединений.
Два уровня архитектуры
PDO Pool состоит из двух слоёв:
1. Ядро PDO (pdo_pool.c) — логика привязки соединений к корутинам,
управление транзакциями, подсчёт ссылок от стейтментов.
2. Async Pool (zend_async_pool_t) — универсальный пул ресурсов из async-расширения.
Управляет очередью свободных соединений, лимитами, healthcheck-ами.
Ничего не знает о PDO — работает с абстрактными zval.
Это разделение позволяет использовать один и тот же механизм пулов для любых ресурсов, не только для баз данных.
Диаграмма компонентов
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():
- Если корутина является PHP-объектом — используется
zend_object.handle(последовательный uint32_t) - Для внутренних корутин — адрес указателя, сдвинутый на
ZEND_MM_ALIGNMENT_LOG2
Cleanup при завершении корутины
При привязке соединения к корутине регистрируется callback pdo_pool_cleanup_callback
через coro->event.add_callback(). Когда корутина завершается (нормально или с ошибкой),
callback автоматически возвращает соединение в пул. Это гарантирует отсутствие утечек
соединений даже при необработанных исключениях.
Pinning: закрепление соединения
Соединение закреплено за корутиной и не вернётся в пул, если выполняется хотя бы одно условие:
conn->in_txn == true— активная транзакцияconn->pool_slot_refcount > 0— есть живые стейтменты (PDOStatement), использующие это соединение
Refcount увеличивается при создании стейтмента и уменьшается при его уничтожении.
Когда оба условия сняты, pdo_pool_maybe_release() возвращает соединение в пул.
Управление credentials в factory
При создании нового соединения pdo_pool_factory() копирует строки
DSN, username и password из template через estrdup(). Это необходимо, потому что
драйверы могут мутировать эти поля во время db_handle_factory():
- PostgreSQL — заменяет
;на пробелы вdata_source - MySQL — аллоцирует
username/passwordиз DSN, если они не были переданы - ODBC — полностью перестраивает
data_source, встраивая credentials
После успешного вызова db_handle_factory() копии освобождаются через efree().
При ошибке освобождение происходит через pdo_pool_free_conn(),
которая также используется деструктором пула.
Несовместимость с persistent connections
Persistent-соединения (PDO::ATTR_PERSISTENT) несовместимы с пулом.
Persistent-соединение привязано к процессу и переживает запрос,
а пул создаёт соединения на уровне запроса с автоматическим управлением жизненным циклом.
Попытка включить оба атрибута одновременно приведёт к ошибке.
Дальше что?
- PDO Pool: пул соединений — руководство по использованию
- Корутины — как работают корутины
- Scope — управление группами корутин