postgres_fdw: stabilize terminated-connection regression tests

The regression test for postgres_fdw_get_connections(true) assumed that
a terminated remote connection would still remain visible in the FDW
connection cache long enough to be reported as closed with a nonzero
remote_backend_pid.

That assumption is not always valid. postgres_fdw_get_connections()
reports only entries that are still present in ConnectionHash, while
pgfdw_inval_callback() may immediately discard an idle cached connection
(xact_depth == 0) when a relevant invalidation arrives. In CI, that can
happen between terminating the remote backend and querying
postgres_fdw_get_connections(true), causing the function to return no
rows.

Adjust the idle-connection test to accept either outcome: if the cache
entry is still present, verify that it reports the expected server name,
closed status, and nonzero remote backend PID; otherwise treat zero rows
as a legitimate result.

To preserve coverage of the terminated-backend reporting path, add a
separate check inside an explicit transaction. In that case, concurrent
invalidation may mark the connection invalid but cannot discard it
before transaction end, so postgres_fdw_get_connections(true) should
still report the terminated connection as in-use, closed, and associated
with a nonzero remote backend PID.

Backpatch to v18, where the affected postgres_fdw_get_connections(true)
test was introduced.

Reported-by: Robert Haas <robertmhaas@gmail.com>
Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Reviewed-by: Daniel Gustafsson <daniel@yesql.se>
Discussion: https://postgr.es/m/CA+Tgmoax3cHXHsm9OidN4F-xiu16y8q2W8T5dTNFic1Zoo2cOw@mail.gmail.com
Backpatch-through: 18
This commit is contained in:
Fujii Masao 2026-07-17 00:49:26 +09:00
parent e3a27cad46
commit ee8f123da7
2 changed files with 87 additions and 13 deletions

View file

@ -13221,23 +13221,67 @@ SELECT server_name,
loopback | f | t
(1 row)
-- After terminating the remote backend, since the connection is closed,
-- "closed" should be TRUE, or NULL if the connection status check
-- is not available. Despite the termination, remote_backend_pid should
-- still show the non-zero PID of the terminated remote backend.
-- After terminating the remote backend, if the connection entry is still in
-- the cache, "closed" should be TRUE, or NULL if the connection status check
-- is not available, and remote_backend_pid should still show the non-zero PID
-- of the terminated remote backend. Concurrent invalidation can remove the
-- idle cached connection before the next statement, in which case
-- postgres_fdw_get_connections(true) can legitimately return no rows.
DO $$ BEGIN
PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity
WHERE application_name = 'fdw_conn_check';
END $$;
SELECT server_name,
WITH terminated_conn AS (
SELECT server_name,
CASE WHEN closed IS NOT false THEN true ELSE false END AS closed,
remote_backend_pid <> 0 AS remote_backend_pid
FROM postgres_fdw_get_connections(true)
)
SELECT CASE
WHEN count(*) = 0 THEN true
WHEN count(*) = 1 THEN bool_and(server_name = 'loopback'
AND closed
AND remote_backend_pid)
ELSE false
END AS ok
FROM terminated_conn;
ok
----
t
(1 row)
-- In an explicit transaction, concurrent invalidation may mark the
-- connection invalid but cannot discard it before transaction end, so the
-- terminated connection should remain visible in the cache.
SELECT 1 FROM postgres_fdw_disconnect_all();
?column?
----------
1
(1 row)
SET client_min_messages = 'ERROR';
BEGIN;
SELECT 1 FROM ft1 LIMIT 1;
?column?
----------
1
(1 row)
DO $$ BEGIN
PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity
WHERE application_name = 'fdw_conn_check';
END $$;
SELECT server_name, used_in_xact,
CASE WHEN closed IS NOT false THEN true ELSE false END AS closed,
remote_backend_pid <> 0 AS remote_backend_pid
FROM postgres_fdw_get_connections(true);
server_name | closed | remote_backend_pid
-------------+--------+--------------------
loopback | t | t
server_name | used_in_xact | closed | remote_backend_pid
-------------+--------------+--------+--------------------
loopback | t | t | t
(1 row)
ABORT;
RESET client_min_messages;
-- Clean up
\set VERBOSITY default
RESET debug_discard_caches;

View file

@ -4748,18 +4748,48 @@ SELECT server_name,
WHERE application_name = 'fdw_conn_check') AS remote_backend_pid
FROM postgres_fdw_get_connections(true);
-- After terminating the remote backend, since the connection is closed,
-- "closed" should be TRUE, or NULL if the connection status check
-- is not available. Despite the termination, remote_backend_pid should
-- still show the non-zero PID of the terminated remote backend.
-- After terminating the remote backend, if the connection entry is still in
-- the cache, "closed" should be TRUE, or NULL if the connection status check
-- is not available, and remote_backend_pid should still show the non-zero PID
-- of the terminated remote backend. Concurrent invalidation can remove the
-- idle cached connection before the next statement, in which case
-- postgres_fdw_get_connections(true) can legitimately return no rows.
DO $$ BEGIN
PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity
WHERE application_name = 'fdw_conn_check';
END $$;
SELECT server_name,
WITH terminated_conn AS (
SELECT server_name,
CASE WHEN closed IS NOT false THEN true ELSE false END AS closed,
remote_backend_pid <> 0 AS remote_backend_pid
FROM postgres_fdw_get_connections(true)
)
SELECT CASE
WHEN count(*) = 0 THEN true
WHEN count(*) = 1 THEN bool_and(server_name = 'loopback'
AND closed
AND remote_backend_pid)
ELSE false
END AS ok
FROM terminated_conn;
-- In an explicit transaction, concurrent invalidation may mark the
-- connection invalid but cannot discard it before transaction end, so the
-- terminated connection should remain visible in the cache.
SELECT 1 FROM postgres_fdw_disconnect_all();
SET client_min_messages = 'ERROR';
BEGIN;
SELECT 1 FROM ft1 LIMIT 1;
DO $$ BEGIN
PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity
WHERE application_name = 'fdw_conn_check';
END $$;
SELECT server_name, used_in_xact,
CASE WHEN closed IS NOT false THEN true ELSE false END AS closed,
remote_backend_pid <> 0 AS remote_backend_pid
FROM postgres_fdw_get_connections(true);
ABORT;
RESET client_min_messages;
-- Clean up
\set VERBOSITY default