Commit 19deb91002a for php.net
commit 19deb91002a53958893903aa3315e30e3285e614
Author: 武田 憲太郎 <takeda@youmind.jp>
Date: Mon Nov 24 12:03:38 2025 +0000
pdo_pgsql: Reset persistent session state on disconnect-equivalent processing
close GH-20572
diff --git a/NEWS b/NEWS
index 1ec90015ef2..af1bb92003d 100644
--- a/NEWS
+++ b/NEWS
@@ -30,6 +30,10 @@ PHP NEWS
. Fixed bug GH-20051 (apache2 shutdowns when restart is requested during
preloading). (Arnaud, welcomycozyhom)
+- PDO_PGSQL:
+ . Clear session-local state disconnect-equivalent processing.
+ (KentarouTakeda)
+
- Phar:
. Support reference values in Phar::mungServer(). (ndossche)
. Invalid values now throw in Phar::mungServer() instead of being silently
diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c
index be865c1f868..44f70e00236 100644
--- a/ext/pdo_pgsql/pgsql_driver.c
+++ b/ext/pdo_pgsql/pgsql_driver.c
@@ -1340,6 +1340,20 @@ static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, i
}
}
+static void pdo_pgsql_request_shutdown(pdo_dbh_t *dbh)
+{
+ PGresult *res;
+ pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
+
+ if(H->server) {
+ res = PQexec(H->server, "DISCARD ALL");
+
+ if(res) {
+ PQclear(res);
+ }
+ }
+}
+
static bool pdo_pgsql_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
{
bool bval;
@@ -1383,7 +1397,7 @@ static const struct pdo_dbh_methods pgsql_methods = {
pdo_pgsql_get_attribute,
pdo_pgsql_check_liveness, /* check_liveness */
pdo_pgsql_get_driver_methods, /* get_driver_methods */
- NULL,
+ pdo_pgsql_request_shutdown,
pgsql_handle_in_transaction,
NULL, /* get_gc */
pdo_pgsql_scanner
diff --git a/ext/pdo_pgsql/tests/session_state_reset.phpt b/ext/pdo_pgsql/tests/session_state_reset.phpt
new file mode 100644
index 00000000000..ad892bb0da5
--- /dev/null
+++ b/ext/pdo_pgsql/tests/session_state_reset.phpt
@@ -0,0 +1,55 @@
+--TEST--
+Persistent connections: session state reset when performing disconnect-equivalent processing (general case)
+--EXTENSIONS--
+pdo_pgsql
+--SKIPIF--
+<?php
+require __DIR__ . '/config.inc';
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+putenv('PDOTEST_ATTR='.serialize([PDO::ATTR_PERSISTENT => true]));
+
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+
+$pdo1 = PDOTest::test_factory(__DIR__ . '/common.phpt');
+
+$pid1 = (int)$pdo1
+ ->query('select pg_backend_pid()::int;')
+ ->fetchColumn(0);
+
+$defaultValue = (int)$pdo1
+ ->query('show log_min_duration_statement;')
+ ->fetchColumn(0);
+
+$setValue = $defaultValue + 1;
+
+$pdo1->exec("set log_min_duration_statement = {$setValue};");
+
+$pdo1 = null;
+
+$pdo2 = PDOTest::test_factory(__DIR__ . '/common.phpt');
+
+$pid2 = (int)$pdo2
+ ->query('select pg_backend_pid()::int;')
+ ->fetchColumn(0);
+
+assert($pid1 === $pid2);
+
+$expectedValue = (int)$pdo2
+ ->query('show log_min_duration_statement;')
+ ->fetchColumn(0);
+
+echo "defaultValue: {$defaultValue}\n";
+echo "setValue: {$setValue}\n";
+echo "expectedValue: {$expectedValue}\n";
+echo "expected value should be reset to default: " . (($expectedValue === $defaultValue) ? 'success' : 'failure') . "\n";
+
+?>
+--EXPECTF--
+defaultValue: %i
+setValue: %d
+expectedValue: %i
+expected value should be reset to default: success
diff --git a/ext/pdo_pgsql/tests/session_state_reset_advisory_lock.phpt b/ext/pdo_pgsql/tests/session_state_reset_advisory_lock.phpt
new file mode 100644
index 00000000000..3435f6b7b40
--- /dev/null
+++ b/ext/pdo_pgsql/tests/session_state_reset_advisory_lock.phpt
@@ -0,0 +1,51 @@
+--TEST--
+Persistent connections: session state reset when performing disconnect-equivalent processing (advisory lock case)
+--EXTENSIONS--
+pdo_pgsql
+--SKIPIF--
+<?php
+require __DIR__ . '/config.inc';
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+putenv('PDOTEST_ATTR='.serialize([PDO::ATTR_PERSISTENT => true]));
+
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+
+$pdo1 = PDOTest::test_factory(__DIR__ . '/common.phpt');
+
+$pid1 = (int)$pdo1
+ ->query('select pg_backend_pid()::int;')
+ ->fetchColumn(0);
+
+$lockResult1 = (bool)$pdo1
+ ->query('select pg_try_advisory_lock(42)::int;')
+ ->fetchColumn(0);
+
+$pdo1 = null;
+
+$dsn = getenv('PDO_PGSQL_TEST_DSN');
+$dsn .= ';';
+putenv('PDO_PGSQL_TEST_DSN='.$dsn);
+
+$pdo2 = PDOTest::test_factory(__DIR__ . '/common.phpt');
+
+$pid2 = (int)$pdo2
+ ->query('select pg_backend_pid()::int;')
+ ->fetchColumn(0);
+
+assert($pid1 !== $pid2);
+
+$lockResult2 = (bool)$pdo2
+ ->query('select pg_try_advisory_lock(42)::int;')
+ ->fetchColumn(0);
+
+echo "lock1: " . ($lockResult1 ? 'success' : 'failure') . "\n";
+echo "lock2: " . ($lockResult2 ? 'success' : 'failure') . "\n";
+
+?>
+--EXPECT--
+lock1: success
+lock2: success
diff --git a/ext/pdo_pgsql/tests/session_state_reset_after_interrupted.phpt b/ext/pdo_pgsql/tests/session_state_reset_after_interrupted.phpt
new file mode 100644
index 00000000000..fdafe8a00fc
--- /dev/null
+++ b/ext/pdo_pgsql/tests/session_state_reset_after_interrupted.phpt
@@ -0,0 +1,52 @@
+--TEST--
+Persistent connections: session state reset after backend termination (interrupted case)
+--EXTENSIONS--
+pdo_pgsql
+--SKIPIF--
+<?php
+require __DIR__ . '/config.inc';
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+putenv('PDOTEST_ATTR='.serialize([PDO::ATTR_PERSISTENT => true]));
+
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+
+$pdo1 = PDOTest::test_factory(__DIR__ . '/common.phpt');
+
+$pid1 = (int)$pdo1
+ ->query('select pg_backend_pid()::int;')
+ ->fetchColumn(0);
+
+$pid1 = (int)$pdo1
+ ->query('select pg_backend_pid()::int;')
+ ->fetchColumn(0);
+
+$dsn = getenv('PDO_PGSQL_TEST_DSN');
+$dsn .= ';';
+putenv('PDO_PGSQL_TEST_DSN='.$dsn);
+
+$pdo2 = PDOTest::test_factory(__DIR__ . '/common.phpt');
+
+$pid2 = (int)$pdo2
+ ->query('select pg_backend_pid()::int;')
+ ->fetchColumn(0);
+
+assert($pid1 !== $pid2);
+
+$terminateResult = (bool)$pdo2
+ ->query("select pg_terminate_backend({$pid1})::int")
+ ->fetchColumn(0);
+
+// Disconnect after being terminated by another connection
+$pdo1 = null;
+
+echo 'pid of pdo1: ' . $pid1 . "\n";
+echo 'terminate result of pdo1 by pdo2: ' . ($terminateResult ? 'success' : 'failure') . "\n";
+
+?>
+--EXPECTF--
+pid of pdo1: %d
+terminate result of pdo1 by pdo2: success