Архітектура Async\Pool

Ця стаття описує внутрішню будову універсального пулу ресурсів. Якщо вас цікавить посібник з використання, дивіться Async\Pool. Для PDO-специфічного шару дивіться Архітектура PDO Pool.

Структура даних

Пул реалізований у два шари: публічна ABI-структура в ядрі PHP та розширена внутрішня структура в розширенні async.

Структури даних пулу

Два шляхи створення

Пул можна створити з PHP-коду (через конструктор Async\Pool) або з C-розширення (через внутрішній API).

Шлях Функція Зворотні виклики Використовується
PHP zend_async_pool_create() zend_fcall_t* (PHP callable) Код користувача
C API zend_async_pool_create_internal() вказівники на функції PDO, інші розширення

Різниця полягає в handler_flags. Коли прапорець встановлено, пул викликає C-функцію напряму, минаючи накладні витрати на виклик PHP callable через zend_call_function().

Acquire: отримання ресурсу

acquire() -- Внутрішній алгоритм

Очікування ресурсу

Коли всі ресурси зайняті і досягнуто max_size, корутина призупиняється через ZEND_ASYNC_SUSPEND(). Механізм очікування аналогічний каналам:

  1. Створюється структура zend_async_pool_waiter_t
  2. Очікувач додається до FIFO-черги waiters
  3. Реєструється зворотний виклик для пробудження
  4. Якщо встановлено таймаут – реєструється таймер
  5. ZEND_ASYNC_SUSPEND() – корутина віддає управління

Пробудження відбувається, коли інша корутина викликає release().

Release: повернення ресурсу

release() -- Внутрішній алгоритм

Healthcheck: фонове моніторування

Якщо healthcheckInterval > 0, при створенні пулу запускається періодичний таймер. Таймер інтегрований з реактором через ZEND_ASYNC_NEW_TIMER_EVENT.

Healthcheck -- Періодична перевірка

Healthcheck перевіряє тільки вільні ресурси. Зайняті ресурси не зачіпаються. Якщо після видалення мертвих ресурсів загальна кількість падає нижче min, пул створює заміни.

Кільцевий буфер

Вільні ресурси зберігаються в кільцевому буфері – ring buffer з фіксованою ємністю. Початкова ємність – 8 елементів, розширюється за потребою.

Операції push та pop виконуються за O(1). Буфер використовує два вказівники (head та tail), що забезпечує ефективне додавання та вилучення ресурсів без переміщення елементів.

Інтеграція з системою подій

Пул успадковує zend_async_event_t та реалізує повний набір обробників подій:

Обробник Призначення
add_callback Реєстрація зворотного виклику (для очікувачів)
del_callback Видалення зворотного виклику
start Запуск події (NOP)
stop Зупинка події (NOP)
dispose Повне очищення: звільнення пам’яті, знищення зворотних викликів

Це дозволяє:

Збирання сміття

PHP-обгортка пулу (async_pool_obj_t) реалізує власний get_gc, який реєструє всі ресурси з idle-буфера як GC-корені. Це запобігає передчасному збиранню вільних ресурсів, на які немає явних посилань з PHP-коду.

Circuit Breaker

Пул реалізує інтерфейс CircuitBreaker з трьома станами:

Стани Circuit Breaker

Переходи можуть бути ручними або автоматичними через CircuitBreakerStrategy:

Close: завершення роботи пулу

При закритті пулу:

  1. Подія пулу позначається як CLOSED
  2. Таймер healthcheck зупиняється
  3. Усі очікуючі корутини пробуджуються з PoolException
  4. Усі вільні ресурси знищуються через destructor
  5. Зайняті ресурси продовжують жити – вони будуть знищені при release

C API для розширень

Розширення (PDO, Redis тощо) використовують пул через макроси:

Макрос Функція
ZEND_ASYNC_NEW_POOL(...) Створити пул із C-зворотними викликами
ZEND_ASYNC_NEW_POOL_OBJ(pool) Створити PHP-обгортку для пулу
ZEND_ASYNC_POOL_ACQUIRE(pool, result, timeout) Отримати ресурс
ZEND_ASYNC_POOL_RELEASE(pool, resource) Повернути ресурс
ZEND_ASYNC_POOL_CLOSE(pool) Закрити пул

Усі макроси викликають вказівники на функції, зареєстровані розширенням async під час завантаження. Це забезпечує ізоляцію: ядро PHP не залежить від конкретної реалізації пулу.

Послідовність: повний цикл Acquire-Release

Повний цикл acquire -> use -> release

Що далі?