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

Ця стаття описує внутрішню будову PDO Pool. Якщо ви шукаєте посібник з використання, див. PDO Pool: Пул з’єднань.

Дворівнева архітектура

PDO Pool складається з двох рівнів:

1. Ядро PDO (pdo_pool.c) — логіка прив’язки з’єднань до корутин, управління транзакціями, підрахунок посилань на вирази.

2. Async Pool (zend_async_pool_t) — універсальний пул ресурсів з асинхронного розширення. Керує чергою вільних з’єднань, лімітами та перевірками стану. Він нічого не знає про PDO — працює з абстрактними значеннями zval.

Таке розділення дозволяє використовувати один і той самий механізм пулінгу для будь-яких ресурсів, не лише для баз даних.

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

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

Шаблонне з’єднання

При створенні PDO з пулом ядро не відкриває реальне TCP-з’єднання. Натомість створюється шаблон — об’єкт pdo_dbh_t, що зберігає DSN, ім’я користувача, пароль та посилання на драйвер. Усі реальні з’єднання створюються пізніше, на вимогу, на основі цього шаблону.

Для шаблону викликається db_handle_init_methods() замість db_handle_factory(). Цей метод встановлює таблицю методів драйвера (dbh->methods), але не створює TCP-з’єднання та не виділяє driver_data.

Життєвий цикл з’єднання

Життєвий цикл з'єднання в пулі

Створення з’єднання з пулу (послідовність)

Створення з'єднання з пулу

Внутрішній API

pdo_pool.c — Публічні функції

Функція Призначення
pdo_pool_create() Створює пул для pdo_dbh_t на основі атрибутів конструктора
pdo_pool_destroy() Звільняє всі з’єднання, закриває пул, очищує хеш-таблицю
pdo_pool_acquire_conn() Повертає з’єднання для поточної корутини (повторне використання або acquire)
pdo_pool_peek_conn() Повертає прив’язане з’єднання без acquire (NULL, якщо немає)
pdo_pool_maybe_release() Повертає з’єднання в пул, якщо немає транзакції або виразів
pdo_pool_get_wrapper() Повертає PHP-об’єкт Async\Pool для методу getPool()

pdo_pool.c — Внутрішні зворотні виклики

Зворотний виклик Коли викликається
pdo_pool_factory() Пулу потрібне нове з’єднання (acquire при порожньому пулі)
pdo_pool_destructor() Пул знищує з’єднання (при закритті або витісненні)
pdo_pool_healthcheck() Періодична перевірка — чи живе з’єднання?
pdo_pool_before_release() Перед поверненням у пул — відкат незафіксованих транзакцій
pdo_pool_free_conn() Закриває з’єднання драйвера, звільняє пам’ять

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

З’єднання прив’язуються до корутин через хеш-таблицю pool_connections, де ключ — ідентифікатор корутини, а значення — вказівник на pdo_dbh_t.

Ідентифікатор корутини обчислюється функцією pdo_pool_coro_key():

Очищення при завершенні корутини

Коли з’єднання прив’язується до корутини, через coro->event.add_callback() реєструється pdo_pool_cleanup_callback. Коли корутина завершується (нормально або з помилкою), зворотний виклик автоматично повертає з’єднання в пул. Це гарантує відсутність витоку з’єднань навіть при необроблених винятках.

Закріплення: блокування з’єднання

З’єднання закріплюється за корутиною і не повертається в пул, якщо виконується хоча б одна умова:

Лічильник посилань інкрементується при створенні виразу та декрементується при його знищенні. Коли обидві умови зняті, pdo_pool_maybe_release() повертає з’єднання в пул.

Управління обліковими даними у фабриці

При створенні нового з’єднання pdo_pool_factory() копіює рядки DSN, імені користувача та пароля з шаблону через estrdup(). Це необхідно, оскільки драйвери можуть змінювати ці поля під час db_handle_factory():

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

Несумісність з постійними з’єднаннями

Постійні з’єднання (PDO::ATTR_PERSISTENT) несумісні з пулом. Постійне з’єднання прив’язане до процесу та переживає запити, тоді як пул створює з’єднання на рівні запиту з автоматичним управлінням життєвим циклом. Спроба увімкнути обидва атрибути одночасно призведе до помилки.

Що далі?