From 362426777abc7c2e2ee469076e1925e58e8c8448 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <lic@highgo.com>
Date: Tue, 3 Feb 2026 11:16:11 +0800
Subject: [PATCH v2 2/2] Add TAP test for logical slot type race

Add an injection point before logical slot acquisition in
StartLogicalReplication so we can reproduce a slot type swap.
Add a recovery TAP test that pauses a logical walsender,
replaces the logical slot with a physical slot of the same
name, and verifies the logical replication error.

Suggested-by: Zhijie Hou <houzj.fnst@fujitsu.com>
Author: Chao Li <lic@highgo.com>
Reviewed-by:
Discussion: https://postgr.es/m/6E7BD4F7-C22A-4B6C-A9BD-62877390DF86@gmail.com
---
 src/backend/replication/walsender.c           |  3 +
 .../recovery/t/052_logical_slot_type_race.pl  | 78 +++++++++++++++++++
 2 files changed, 81 insertions(+)
 create mode 100644 src/test/recovery/t/052_logical_slot_type_race.pl

diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 30d6fa6cedf..b3790f20970 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -78,6 +78,7 @@
 #include "replication/syncrep.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "utils/injection_point.h"
 #include "replication/walsender_private.h"
 #include "storage/condition_variable.h"
 #include "storage/aio_subsys.h"
@@ -1464,6 +1465,8 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
+	INJECTION_POINT("start-logical-replication-before-acquire", NULL);
+
 	ReplicationSlotAcquire(cmd->slotname, true, true);
 	if (!SlotIsLogical(MyReplicationSlot))
 		ereport(ERROR,
diff --git a/src/test/recovery/t/052_logical_slot_type_race.pl b/src/test/recovery/t/052_logical_slot_type_race.pl
new file mode 100644
index 00000000000..e39f5c01294
--- /dev/null
+++ b/src/test/recovery/t/052_logical_slot_type_race.pl
@@ -0,0 +1,78 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+#
+# Test that logical replication correctly rejects a physical replication
+# slot if a race occurs during slot acquisition.
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+use IPC::Run;
+use Config;
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init;
+$node->append_conf('postgresql.conf', qq(
+wal_level = logical
+max_replication_slots = 2
+max_wal_senders = 2
+));
+$node->start;
+
+if (!$node->check_extension('injection_points'))
+{
+	plan skip_all => 'Extension injection_points not installed';
+}
+
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+$node->safe_psql('postgres',
+	q{SELECT pg_create_logical_replication_slot('race_slot', 'pgoutput');});
+
+$node->safe_psql('postgres',
+	q{SELECT injection_points_attach('start-logical-replication-before-acquire', 'wait');});
+
+my $stderr = '';
+my $pg_recvlogical = IPC::Run::start(
+	[
+		'pg_recvlogical',
+		'--dbname' => $node->connstr('postgres'),
+		'--slot' => 'race_slot',
+		'--plugin' => 'pgoutput',
+		'--file' => '-',
+		'--start',
+		'--no-loop',
+	],
+	'2>' => \$stderr);
+
+# Wait for the walsender to reach the injection point.
+$node->wait_for_event('walsender',
+	'start-logical-replication-before-acquire');
+
+# Replace logical slot with a physical slot of the same name.
+$node->safe_psql('postgres', q{SELECT pg_drop_replication_slot('race_slot');});
+$node->safe_psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('race_slot');});
+
+# Resume the walsender.
+$node->safe_psql('postgres',
+	q{SELECT injection_points_wakeup('start-logical-replication-before-acquire');});
+
+$pg_recvlogical->finish;
+my $return = $?;
+
+cmp_ok($return, '!=', 0, 'pg_recvlogical exited non-zero');
+like($stderr,
+	qr/cannot use a physical replication slot for logical replication/,
+	'logical replication refused physical slot');
+
+$node->safe_psql('postgres', q{SELECT pg_drop_replication_slot('race_slot');});
+
+done_testing();
-- 
2.50.1 (Apple Git-155)

