Skip to content

Conversation

@EdmondDantes
Copy link

No description provided.

EdmondDantes and others added 18 commits February 3, 2026 07:59
- Add function pointer types for pool operations (new_pool, acquire, release, close)
- Add global function pointers with stub implementations
- Add ZEND_ASYNC_NEW_POOL and ZEND_ASYNC_POOL_* macros
- Add zend_async_pool_api_register() for extension registration
Implements transparent connection pooling for PDO when the async
extension is available. Each coroutine gets its own connection from
the pool, with automatic cleanup when the coroutine finishes.

New PDO attributes for pool configuration:
- PDO::ATTR_POOL_ENABLED - Enable connection pooling
- PDO::ATTR_POOL_MIN - Minimum pool size
- PDO::ATTR_POOL_MAX - Maximum pool size
- PDO::ATTR_POOL_HEALTHCHECK_INTERVAL - Health check interval (ms)

New PDO method:
- getPool(): Returns Async\Pool object for CircuitBreaker configuration

Key implementation details:
- Pool created via zend_async_API.h C-level functions
- Connections mapped per coroutine in HashTable
- Transaction state (in_txn) tracked per pooled connection
- Uncommitted transactions rolled back on connection release
- Automatic connection release via coroutine event callback

Files added:
- ext/pdo/pdo_pool.c - Pool implementation
- ext/pdo/pdo_pool.h - Pool API and helper macros
Tests require true_async and pdo_mysql extensions:

- pdo_pool_001_attributes: Pool attribute constants exist
- pdo_pool_002_getPool_no_pool: getPool() returns null when disabled
- pdo_pool_003_pool_creation: Pool creation with ATTR_POOL_ENABLED
- pdo_pool_004_coroutine_connections: Each coroutine gets own connection
- pdo_pool_005_transactions: Transaction state per pooled connection
- pdo_pool_006_auto_rollback: Uncommitted txn rolled back on release
- pdo_pool_007_pool_stats: Pool statistics via getPool()
- pdo_pool_008_no_pool_persistent: Pool not created for persistent conn
…eaks

Replace driver_data swapping with per-coroutine connection slots
(pool_connections[coro_id]) and stmt->pooled_conn references. Template
pdo_dbh_t now has driver_data=NULL when pool is enabled — drivers receive
valid pdo_dbh_t* via dispatch layer and need no pool awareness.

Key changes:
- Add db_handle_init_methods to pdo_driver_t (all 6 drivers)
- Dispatch acquire/release at PDO level (pdo_dbh.c, pdo_stmt.c)
- Fix pool memory leak: eager wrapper creation owns pool lifecycle
- Fix destruction order: release connections before destroying pool
- Route error_code via pdo_pool_sync_error for correct error reporting
- Reject ATTR_POOL_ENABLED + ATTR_PERSISTENT with PDOException
- Fix strcmp without explicit comparison in PDO_HANDLE_DBH_ERR
- Remove all debug output (PDO_POOL_DBG macros)
- Fix tests for deterministic output and correct connection lifetime
Avoid estrdup/efree for data_source, username, password in pooled
connection factory. Borrow pointers from template dbh for the duration
of the driver factory call, then null them out. Remove redundant field
copies that drivers don't read during factory.
Avoid estrdup/efree for data_source, username, password in pooled
connection factory. Borrow pointers from template dbh for the duration
of the driver factory call, then null them out. Remove redundant field
copies that drivers don't read during factory.

Replace direct zend_async_new_pool_fn/zend_async_new_pool_obj_fn calls
with ZEND_ASYNC_NEW_POOL/ZEND_ASYNC_NEW_POOL_OBJ macros for consistency
with the rest of the async API usage.
…apper

- Replace direct zend_async_*_fn calls with ZEND_ASYNC_NEW_POOL /
  ZEND_ASYNC_NEW_POOL_OBJ macros for consistency with async API.
- Remove pdo_pool_async_available() — TrueAsync is always linked,
  function pointers are never NULL (stubs throw errors if not enabled).
- Create pool wrapper object lazily on first getPool() call or during
  destroy, instead of eagerly at pool creation time.
- Borrow template strings in pool factory instead of estrdup/efree.
…apper

- Replace direct zend_async_*_fn calls with ZEND_ASYNC_NEW_POOL /
  ZEND_ASYNC_NEW_POOL_OBJ macros for consistency with async API.
- Remove pdo_pool_async_available() — TrueAsync is always linked,
  function pointers are never NULL (stubs throw errors if not enabled).
- Create pool wrapper object lazily on first getPool() call instead of
  eagerly at pool creation time.
- Destroy pool via ZEND_ASYNC_POOL_CLOSE + ZEND_ASYNC_EVENT_RELEASE
  using proper event ref counting instead of wrapper hack.
- Borrow template strings in pool factory instead of estrdup/efree.
…sh keys

- Wrap error-path checks with UNEXPECTED() for branch prediction
- Add const qualifiers where connections are read-only
- Introduce pdo_pool_coro_key() helper that uses zend_object handle
  (sequential uint32_t) when available, falling back to pointer address
…or crash

- Add pool_slot_refcount on pooled pdo_dbh_t to track how many
  statements borrow a connection. maybe_release() now checks
  in_txn || pool_slot_refcount > 0 before returning a connection
  to the pool, preventing use-after-release when multiple statements
  share one slot in the same coroutine.

- Add pdo_pool_peek_conn() that returns the existing slot connection
  without acquiring a new one. Used by lastInsertId() (returns false
  when no connection held) and errorInfo() (returns SQLSTATE only,
  native details unavailable) to avoid silently reading wrong state
  from a different connection.

- Fix segfault in _pdo_mysql_error() when called via
  pdo_mysql_error_stmt: stmt->dbh is the pool template with
  driver_data==NULL. Now uses S->H (the statement's own MySQL
  handle) when stmt is provided.

- Add PDOPoolTest helper and 9 new tests covering refcount
  lifecycle, cross-coroutine isolation, txn+refcount interaction,
  lastInsertId and errorInfo with/without active connection.
When stmt is provided, use stmt->driver_data->H (the statement's cached
connection handle) instead of dbh->driver_data, which is NULL when dbh
is the pool template. Fixes potential segfaults in pgsql, sqlite, odbc,
firebird, and dblib drivers (same pattern as the MySQL fix in b89f1cf).
Replace all blocking libpq calls (PQexec, PQexecParams, PQprepare,
PQexecPrepared, PQgetResult, PQclosePrepared) with concurrent wrappers
that yield to the async event loop when inside a coroutine, falling back
to synchronous behavior otherwise. This enables parallel PDO PostgreSQL
queries across coroutines without blocking the event loop.

Concurrent helpers implemented in pgsql_driver.c:
- pdo_pgsql_exec_concurrent: PQsendQuery + flush + collect results
- pdo_pgsql_exec_params_concurrent: PQsendQueryParams variant
- pdo_pgsql_prepare_concurrent: PQsendPrepare variant
- pdo_pgsql_exec_prepared_concurrent: PQsendQueryPrepared variant
- pdo_pgsql_close_prepared_concurrent: PQsendClosePrepared (PG17+)
- pdo_pgsql_get_result_concurrent: async-aware PQgetResult
- pdo_pgsql_flush: async-aware PQflush for unbuffered send paths
Add a `bool *supports_pool` parameter to `db_handle_init_methods`.
Only MySQL and PgSQL set the flag to true. When pool is enabled for
an unsupported driver, throw an exception instead of silently
proceeding.
…on bugs

Drivers may mutate, reallocate, or free data_source/username/password
during db_handle_factory (PgSQL replaces ';' with ' ', MySQL allocates
username from DSN, ODBC rebuilds the entire connection string). Borrowing
raw pointers from the template caused template corruption, memory leaks,
and potential double-free. Now estrdup copies are made per factory call
and freed afterward. Extracted pdo_pool_free_conn for unified cleanup.
@EdmondDantes EdmondDantes merged commit 3980e3e into true-async Feb 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant