From a18656a6059d5605a62ffd729b10e8f2856a7df8 Mon Sep 17 00:00:00 2001
From: Zhijie Hou <houzj.fnst@fujitsu.com>
Date: Wed, 19 Nov 2025 18:25:48 +0800
Subject: [PATCH v4 2/2] Add a tap-test using injection point

---
 src/backend/access/transam/xlog.c             |  3 +
 .../recovery/t/046_checkpoint_logical_slot.pl | 81 ++++++++++++++++++-
 2 files changed, 82 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 17324259cff..56c69574238 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -7838,6 +7838,9 @@ CreateRestartPoint(int flags)
 	replayPtr = GetXLogReplayRecPtr(&replayTLI);
 	endptr = (receivePtr < replayPtr) ? replayPtr : receivePtr;
 	KeepLogSeg(endptr, &_logSegNo);
+
+	INJECTION_POINT("restartpoint-before-slot-invalidation", NULL);
+
 	if (InvalidateObsoleteReplicationSlots(RS_INVAL_WAL_REMOVED | RS_INVAL_IDLE_TIMEOUT,
 										   _logSegNo, InvalidOid,
 										   InvalidTransactionId))
diff --git a/src/test/recovery/t/046_checkpoint_logical_slot.pl b/src/test/recovery/t/046_checkpoint_logical_slot.pl
index 0242a1573ca..339289c0b59 100644
--- a/src/test/recovery/t/046_checkpoint_logical_slot.pl
+++ b/src/test/recovery/t/046_checkpoint_logical_slot.pl
@@ -20,8 +20,7 @@ if ($ENV{enable_injection_points} ne 'yes')
 my ($node, $result);
 
 $node = PostgreSQL::Test::Cluster->new('mike');
-$node->init;
-$node->append_conf('postgresql.conf', "wal_level = 'logical'");
+$node->init(allows_streaming => 'logical');
 $node->start;
 
 # Check if the extension injection_points is available, as it may be
@@ -139,4 +138,82 @@ eval {
 };
 is($@, '', "Logical slot still valid");
 
+# Verify that while syncing a slot to the standby server, if the WAL before the
+# remote restart_lsn is at risk of being removed by a checkpoint, the slot
+# cannot be synced. Otherwise, even if the slot syncing succeeds, it may be
+# immediately invalidated by the checkpoint.
+my $primary = $node;
+
+$primary->append_conf('postgresql.conf', "autovacuum = off");
+$primary->reload;
+
+my $backup_name = 'backup';
+
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby = PostgreSQL::Test::Cluster->new('standby');
+$standby->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+# Increase the log_min_messages setting to DEBUG2 on both the standby and
+# primary to debug test failures, if any.
+my $connstr_1 = $primary->connstr;
+$standby->append_conf(
+	'postgresql.conf', qq(
+hot_standby_feedback = on
+primary_slot_name = 'phys_slot'
+primary_conninfo = '$connstr_1 dbname=postgres'
+log_min_messages = 'debug2'
+));
+
+$primary->psql('postgres',
+	q{SELECT pg_create_logical_replication_slot('failover_slot', 'test_decoding', false, false, true);
+	 SELECT pg_create_physical_replication_slot('phys_slot');}
+);
+
+$standby->start;
+
+# Generate some activity and switch WAL file on the primary
+$primary->advance_wal(1);
+$primary->psql('postgres', "CHECKPOINT");
+$primary->wait_for_replay_catchup($standby);
+
+# checkpoint on the standby and make it wait on the injection point so that the
+# checkpoint stops right before invalidating replication slots.
+note('starting checkpoint');
+
+$checkpoint = $standby->background_psql('postgres');
+$checkpoint->query_safe(
+	q(select injection_points_attach('restartpoint-before-slot-invalidation','wait'))
+);
+$checkpoint->query_until(
+	qr/starting_checkpoint/,
+	q(\echo starting_checkpoint
+checkpoint;
+\q
+));
+
+# Wait until the checkpoint stops right before invalidating slots
+note('waiting for injection_point');
+$standby->wait_for_event('checkpointer', 'restartpoint-before-slot-invalidation');
+note('injection_point is reached');
+
+# Synchronize the failover slot to the standby
+$standby->safe_psql('postgres', "SELECT pg_sync_replication_slots();");
+
+# Confirm that the logical slot is not synced
+is( $standby->safe_psql(
+		'postgres',
+		q{SELECT count(*) = 0 FROM pg_replication_slots WHERE slot_name = 'failover_slot';}
+	),
+	"t",
+	'logical slot is not synced because the required WALs could be potentially removed');
+
+$standby->safe_psql('postgres',
+	q{select injection_points_wakeup('restartpoint-before-slot-invalidation');
+	  select injection_points_detach('restartpoint-before-slot-invalidation')});
+
 done_testing();
-- 
2.51.1.windows.1

