From dc35e3d09457fb15c0010fd49104087406d139e8 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Mon, 12 Jan 2026 15:42:03 -0400 Subject: [PATCH] Provide mechanism for PDO to reconnect with a persistent connection The persistent connection may be in a wedged or otherwise undesirable state that the connection liveness check has failed to deal with. If so, provide userland code a way to get rid of a persistent connection and create a new one in its place. I'm not entirely pleased with this approach, but perhaps there is a better one. --- ext/pdo/pdo_dbh.c | 16 +++++++++++++--- ext/pdo/pdo_dbh.stub.php | 2 ++ ext/pdo/pdo_dbh_arginfo.h | 8 +++++++- ext/pdo/php_pdo_driver.h | 1 + 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 34f19e364faa7..e6efd3802e2cf 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -304,7 +304,7 @@ static bool create_driver_specific_pdo_object(pdo_driver_t *driver, zend_class_e PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zend_object *current_object, zend_class_entry *called_scope, zval *new_zval_object) { pdo_dbh_t *dbh = NULL; - bool is_persistent = 0; + bool is_persistent = 0, new_persistent_connection = 0; char *data_source; size_t data_source_len; char *colon; @@ -408,6 +408,16 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen username ? username : "", password ? password : ""); } + + /* + * Sometimes, a persistent DBH may be in a wedged + * (that liveness checks may fail to catch) or + * otherwise undesirable state, so provide for a way + * to reset the persistent DBH by making a new one. + */ + if ((v = zend_hash_index_find_deref(Z_ARRVAL_P(options), PDO_ATTR_PERSISTENT_NEW_CONNECTION)) != NULL) { + new_persistent_connection = zval_get_long(v) ? 1 : 0; + } } if (is_persistent) { @@ -416,8 +426,8 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen if (le->type == php_pdo_list_entry()) { pdbh = (pdo_dbh_t*)le->ptr; - /* is the connection still alive ? */ - if (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh)) { + /* is the connection still alive (or force a new one)? */ + if (new_persistent_connection || (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh))) { /* nope... need to kill it */ pdbh->refcount--; zend_list_close(le); diff --git a/ext/pdo/pdo_dbh.stub.php b/ext/pdo/pdo_dbh.stub.php index 7fcec0226b0ba..dd96b74f0c22b 100644 --- a/ext/pdo/pdo_dbh.stub.php +++ b/ext/pdo/pdo_dbh.stub.php @@ -121,6 +121,8 @@ class PDO public const int ATTR_DEFAULT_FETCH_MODE = UNKNOWN; /** @cvalue LONG_CONST(PDO_ATTR_DEFAULT_STR_PARAM) */ public const int ATTR_DEFAULT_STR_PARAM = UNKNOWN; + /** @cvalue LONG_CONST(PDO_ATTR_PERSISTENT_NEW_CONNECTION) */ + public const int ATTR_PERSISTENT_NEW_CONNECTION = UNKNOWN; /** @cvalue LONG_CONST(PDO_ERRMODE_SILENT) */ public const int ERRMODE_SILENT = UNKNOWN; diff --git a/ext/pdo/pdo_dbh_arginfo.h b/ext/pdo/pdo_dbh_arginfo.h index 90da5123a4874..04d63877fece3 100644 --- a/ext/pdo/pdo_dbh_arginfo.h +++ b/ext/pdo/pdo_dbh_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit pdo_dbh.stub.php instead. - * Stub hash: 006be61b2c519e7d9ca997a7f12135eb3e0f3500 */ + * Stub hash: 8f155cc3c779acd9e442d92d628ebe802aef2338 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, dsn, IS_STRING, 0) @@ -451,6 +451,12 @@ static zend_class_entry *register_class_PDO(void) zend_declare_typed_class_constant(class_entry, const_ATTR_DEFAULT_STR_PARAM_name, &const_ATTR_DEFAULT_STR_PARAM_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release_ex(const_ATTR_DEFAULT_STR_PARAM_name, true); + zval const_ATTR_PERSISTENT_NEW_CONNECTION_value; + ZVAL_LONG(&const_ATTR_PERSISTENT_NEW_CONNECTION_value, LONG_CONST(PDO_ATTR_PERSISTENT_NEW_CONNECTION)); + zend_string *const_ATTR_PERSISTENT_NEW_CONNECTION_name = zend_string_init_interned("ATTR_PERSISTENT_NEW_CONNECTION", sizeof("ATTR_PERSISTENT_NEW_CONNECTION") - 1, true); + zend_declare_typed_class_constant(class_entry, const_ATTR_PERSISTENT_NEW_CONNECTION_name, &const_ATTR_PERSISTENT_NEW_CONNECTION_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_ATTR_PERSISTENT_NEW_CONNECTION_name, true); + zval const_ERRMODE_SILENT_value; ZVAL_LONG(&const_ERRMODE_SILENT_value, LONG_CONST(PDO_ERRMODE_SILENT)); zend_string *const_ERRMODE_SILENT_name = zend_string_init_interned("ERRMODE_SILENT", sizeof("ERRMODE_SILENT") - 1, true); diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index 9c5986ff8bce8..afe9259f25495 100644 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -123,6 +123,7 @@ enum pdo_attribute_type { PDO_ATTR_DEFAULT_FETCH_MODE, /* Set the default fetch mode */ PDO_ATTR_EMULATE_PREPARES, /* use query emulation rather than native */ PDO_ATTR_DEFAULT_STR_PARAM, /* set the default string parameter type (see the PDO::PARAM_STR_* magic flags) */ + PDO_ATTR_PERSISTENT_NEW_CONNECTION, /* throw away old persistent connection and make a new one */ /* this defines the start of the range for driver specific options. * Drivers should define their own attribute constants beginning with this