Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ext/pgsql/pgsql.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ static zend_function *pgsql_link_get_constructor(zend_object *object) {
return NULL;
}

static void _php_pgsql_notice_handler(void *l, const char *message);

static void pgsql_link_free(pgsql_link_handle *link)
{
PGresult *res;
Expand All @@ -187,6 +189,12 @@ static void pgsql_link_free(pgsql_link_handle *link)

zend_hash_del(&PGG(connections), link->hash);

if (link->persistent) {
/* Reset the notice processor context to NULL so that notices emitted
* during a subsequent PQreset on this persistent PGconn are safely
* ignored rather than written to a stale link handle. */
PQsetNoticeProcessor(link->conn, _php_pgsql_notice_handler, NULL);
}
link->conn = NULL;
zend_string_release(link->hash);

Expand Down
67 changes: 67 additions & 0 deletions ext/pgsql/tests/gh21575.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
--TEST--
GH-21575 - Memory leak with notices in pgsql persistent connections.
--EXTENSIONS--
pgsql
--SKIPIF--
<?php
include("inc/skipif.inc");

_skip_lc_messages($conn);

$res = @pg_query($conn, "CREATE OR REPLACE FUNCTION test_notice() RETURNS boolean AS '
begin
RAISE NOTICE ''test notice'';
return ''t'';
end;
' LANGUAGE plpgsql;");
if (!$res) die('skip PLPGSQL not available');
?>
--INI--
pgsql.ignore_notice=0
--FILE--
<?php
include('inc/config.inc');
include('inc/lcmess.inc');

$db = pg_pconnect($conn_str);
_set_lc_messages($db);

pg_query($db, "CREATE OR REPLACE FUNCTION test_notice() RETURNS boolean AS '
begin
RAISE NOTICE ''test notice'';
return ''t'';
end;
' LANGUAGE plpgsql;");

/* Trigger a notice so the notice handler writes into the link handle. */
pg_query($db, 'SET client_min_messages TO NOTICE;');
pg_query($db, 'SELECT test_notice()');
var_dump(pg_last_notice($db));
pg_close($db);

/* Reconnect with PGSQL_CONNECT_FORCE_NEW which calls PQreset().
* Before the fix, notices emitted during PQreset (e.g. collation version
* mismatch) would write into the stale (freed) link handle, causing a
* memory leak. We cannot reliably trigger such notices in a portable test,
* but this exercises the persistent reconnect + notice handling path. */
$db2 = pg_pconnect($conn_str, PGSQL_CONNECT_FORCE_NEW);
var_dump($db2 instanceof PgSql\Connection);
pg_query($db2, 'SELECT test_notice()');
var_dump(pg_last_notice($db2));

pg_close($db2);

echo "Done\n";
?>
--CLEAN--
<?php
include('inc/config.inc');
$db = pg_connect($conn_str);
pg_query($db, 'DROP FUNCTION IF EXISTS test_notice()');
pg_close($db);
?>
--EXPECTF--
string(%d) "NOTICE: test notice"
bool(true)
string(%d) "NOTICE: test notice"
Done
Loading