From 67136a69201f0fda22cd7c79e5e02e2d1bfc3380 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 30 Apr 2025 14:15:23 +0900
Subject: [PATCH 3/3] Add regression test for 2PC visibility check on standby

This adds some test coverage for a defect fixed in 2e57790836c6, where
the only reliable test back then was to use a hardcoded sleep().

This test relies on an injection point that is persisted across a node
restart, so as it is possible to cause the checkpointer to wait when
configuring the shared memory state of shared_preload_libraries at a
very early startup stage.

Reverting 2e57790836c6 causes the test to fail, and it passes on HEAD.
---
 src/backend/replication/syncrep.c   |  3 ++
 src/test/recovery/t/009_twophase.pl | 60 +++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index cc35984ad008..d02633619c28 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -84,6 +84,7 @@
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc_hooks.h"
+#include "utils/injection_point.h"
 #include "utils/ps_status.h"
 
 /* User-settable parameters for sync rep */
@@ -968,6 +969,8 @@ SyncRepUpdateSyncStandbysDefined(void)
 	if (sync_standbys_defined !=
 		((WalSndCtl->sync_standbys_status & SYNC_STANDBY_DEFINED) != 0))
 	{
+		INJECTION_POINT("checkpointer-syncrep-update");
+
 		LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
 
 		/*
diff --git a/src/test/recovery/t/009_twophase.pl b/src/test/recovery/t/009_twophase.pl
index 1a662ebe499d..f14da1549bac 100644
--- a/src/test/recovery/t/009_twophase.pl
+++ b/src/test/recovery/t/009_twophase.pl
@@ -51,6 +51,27 @@ $node_paris->append_conf(
 ));
 $node_paris->start;
 
+# Check if the extension injection_points is available, as it may be
+# possible that this script is run with installcheck, where the module
+# would not be installed by default.
+my $injection_points_supported =
+  $node_london->check_extension('injection_points');
+if ($injection_points_supported != 0)
+{
+	$node_london->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+	# Set shared_preload_libraries, to allow the injection points to persist
+	# across restarts.
+	$node_london->append_conf(
+		'postgresql.conf', qq(
+		shared_preload_libraries = 'injection_points'
+	));
+	$node_paris->append_conf(
+		'postgresql.conf', qq(
+		shared_preload_libraries = 'injection_points'
+	));
+}
+
 # Switch to synchronous replication in both directions
 configure_and_reload($node_london, "synchronous_standby_names = 'paris'");
 configure_and_reload($node_paris, "synchronous_standby_names = 'london'");
@@ -327,6 +348,23 @@ $cur_primary->psql(
 	INSERT INTO t_009_tbl_standby_mvcc VALUES (2, 'issued to ${cur_primary_name}');
 	PREPARE TRANSACTION 'xact_009_standby_mvcc';
 	");
+
+# Attach an injection point to wait in the checkpointer when configuring
+# the shared memory state data related to synchronous_standby_names, then
+# persist the attached point to disk so as the follow-up restart will be able
+# to wait at the early stages of the checkpointer startup sequence.
+#
+# Note that as the checkpointer has already applied the
+# synchronous_standby_names configuration, this has no effect until the
+# next startup of the primary.
+if ($injection_points_supported != 0)
+{
+	$cur_primary->psql('postgres',
+		"SELECT injection_points_attach('checkpointer-syncrep-update', 'wait')"
+	);
+	$cur_primary->psql('postgres', "SELECT injection_points_flush()");
+}
+
 $cur_primary->stop;
 $cur_standby->restart;
 
@@ -341,6 +379,16 @@ is($psql_out, '0',
 
 # Commit the transaction in primary
 $cur_primary->start;
+
+# Make sure that the checkpointer is waiting before setting up the data of
+# synchronous_standby_names in shared memory.  We want the checkpointer to be
+# stuck and make sure that the next COMMIT PREPARED is detected correctly on
+# the standby when remote_apply is set on the primary.
+if ($injection_points_supported != 0)
+{
+	$cur_primary->wait_for_event('checkpointer',
+		'checkpointer-syncrep-update');
+}
 $cur_primary->psql(
 	'postgres', "
 SET synchronous_commit='remote_apply'; -- To ensure the standby is caught up
@@ -361,6 +409,18 @@ is($psql_out, '2',
 	"Committed prepared transaction is visible to new snapshot in standby");
 $standby_session->quit;
 
+# Remove the injection point, the checkpointer now applies the configuration
+# related to synchronous_standby_names in shared memory.
+if ($injection_points_supported != 0)
+{
+	$cur_primary->psql('postgres',
+		"SELECT injection_points_wakeup('checkpointer-syncrep-update')");
+	$cur_primary->psql('postgres',
+		"SELECT injection_points_detach('checkpointer-syncrep-update')");
+}
+
+$cur_standby->restart;
+
 ###############################################################################
 # Check for a lock conflict between prepared transaction with DDL inside and
 # replay of XLOG_STANDBY_LOCK wal record.
-- 
2.49.0

