[PHP-CVS] [php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5

[email protected] (Ilia Alshanetsky)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-18T19:33:58-04:00

Commit: https://github.com/php/php-src/commit/830c9d0f9eedc8396d0b301c38105fa7433572ee
Raw diff: https://github.com/php/php-src/commit/830c9d0f9eedc8396d0b301c38105fa7433572ee.diff

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  Fix leak when persistent PDO liveness check fails

Changed paths:
  A  ext/pdo_mysql/tests/persistent_liveness_evict.phpt
  M  NEWS
  M  ext/pdo/pdo_dbh.c


Diff:

diff --git a/NEWS b/NEWS
index 149cfc869e3f..185c4799343b 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,10 @@ PHP                                                                        NEWS
 - Opcache:
   . Fixed opcache.protect_memory race under ZTS. (realFlowControl)
 
+- PDO:
+  . Fixed a leak when a persistent connection failed a liveness check
+    with no other live PDO handle. (iliaal)
+
 - Readline:
   . Fixed the interactive shell not waiting for the pager process to exit.
     (Weilin Du)
diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index 08e398ea3682..43c2cf8c0945 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -418,9 +418,12 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen
 
 					/* is the connection still alive ? */
 					if (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh)) {
-						/* nope... need to kill it */
-						pdbh->refcount--;
-						zend_list_close(le);
+						if (pdbh->refcount > 1) {
+							pdbh->refcount--;
+							zend_list_close(le);
+						} else {
+							zend_hash_del(&EG(persistent_list), hash_key);
+						}
 						pdbh = NULL;
 					}
 				}
diff --git a/ext/pdo_mysql/tests/persistent_liveness_evict.phpt b/ext/pdo_mysql/tests/persistent_liveness_evict.phpt
new file mode 100644
index 000000000000..545c16ebdbdb
--- /dev/null
+++ b/ext/pdo_mysql/tests/persistent_liveness_evict.phpt
@@ -0,0 +1,42 @@
+--TEST--
+Persistent reconnect after a dead cached handle does not leak the old pdo_dbh_t
+--EXTENSIONS--
+pdo_mysql
+--ENV--
+VALGRIND_OPTS=--leak-check=full
+--SKIPIF--
+<?php
+require_once __DIR__ . '/inc/mysql_pdo_test.inc';
+MySQLPDOTest::skip();
+if (!MySQLPDOTest::isPDOMySQLnd()) {
+    die('skip mysqlnd only (libmysql mysql_ping auto-reconnects)');
+}
+?>
+--FILE--
+<?php
+require_once __DIR__ . '/inc/mysql_pdo_test.inc';
+
+$dsn = MySQLPDOTest::getDSN();
+$user = PDO_MYSQL_TEST_USER;
+$pass = PDO_MYSQL_TEST_PASS;
+$opts = [
+    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+    PDO::ATTR_PERSISTENT => true,
+];
+
+$cached = new PDO($dsn, $user, $pass, $opts);
+$id = (int) $cached->query('SELECT CONNECTION_ID()')->fetchColumn();
+unset($cached);
+
+$killer = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
+$killer->exec('KILL ' . $id);
+unset($killer);
+
+$next = new PDO($dsn, $user, $pass, $opts);
+$nextId = (int) $next->query('SELECT CONNECTION_ID()')->fetchColumn();
+var_dump($nextId !== $id);
+echo $next->query('SELECT 1')->fetchColumn(), "\n";
+?>
+--EXPECT--
+bool(true)
+1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.