Commit 31098001226 for php
commit 31098001226e6a39f251c9ad78328d4c0bf4a231
Author: Lazizbek Ergashev <lazerg2@gmail.com>
Date: Tue Sep 29 02:52:45 2026 +0500
Fix GH-23962: Don't roll back a persistent PDO handle still in use (#23963)
diff --git a/NEWS b/NEWS
index d36d4b56bfc..4ea8ab37052 100644
--- a/NEWS
+++ b/NEWS
@@ -92,6 +92,8 @@ PHP NEWS
column index. (Ilia Alshanetsky)
. Fixed PDOStatement::bindColumn() registering a binding for a column name
that is not in the result set. (Ilia Alshanetsky)
+ . Fixed bug GH-23962 (Destroying a persistent PDO instance rolls back a
+ transaction still in use by another instance). (Lazizbek Ergashev)
- Readline:
. Fixed a heap over-read in the interactive shell prompt when cli.prompt is
diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index 9f3d20745df..1dc43c2b972 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -1521,7 +1521,9 @@ static void pdo_dbh_free_storage(zend_object *std)
return;
}
- if (dbh->driver_data && dbh->methods && dbh->methods->rollback && pdo_is_in_transaction(dbh)) {
+ /* The persistent list holds one reference, other objects may hold the rest */
+ if (dbh->driver_data && dbh->methods && dbh->methods->rollback
+ && (!dbh->is_persistent || dbh->refcount <= 2) && pdo_is_in_transaction(dbh)) {
dbh->methods->rollback(dbh);
dbh->in_txn = false;
}
diff --git a/ext/pdo_sqlite/tests/gh23962.phpt b/ext/pdo_sqlite/tests/gh23962.phpt
new file mode 100644
index 00000000000..d4c1cc4e4e0
--- /dev/null
+++ b/ext/pdo_sqlite/tests/gh23962.phpt
@@ -0,0 +1,31 @@
+--TEST--
+GH-23962 (Destroying a persistent PDO instance rolls back a transaction still in use by another instance)
+--CREDITS--
+BoyeMagnus
+--EXTENSIONS--
+pdo_sqlite
+--FILE--
+<?php
+$dsn = 'sqlite::memory:';
+
+$a = new PDO($dsn, null, null, [PDO::ATTR_PERSISTENT => true]);
+$b = new PDO($dsn, null, null, [PDO::ATTR_PERSISTENT => true]);
+
+$b->beginTransaction();
+unset($a);
+var_dump($b->inTransaction());
+var_dump($b->commit());
+
+$b->beginTransaction();
+$b->exec('CREATE TABLE test (a int)');
+unset($b);
+
+$c = new PDO($dsn, null, null, [PDO::ATTR_PERSISTENT => true]);
+var_dump($c->inTransaction());
+var_dump($c->query("SELECT count(*) FROM sqlite_master WHERE name = 'test'")->fetchColumn());
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(false)
+int(0)