summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFujii Masao2026-08-27 16:17:19 +0000
committerFujii Masao2026-08-27 16:17:19 +0000
commitaec25d6107ddf03430947f67d662bdedffc064c2 (patch)
tree5f6d7573ae48e03bf1f1619a0a693ff7d899e074
parent321ec8ee1c016fde47aeebb9e0b3882bcc4c6b97 (diff)
Fix temporary WAL receiver slot handling on timeline switches
Previously, when wal_receiver_create_temp_slot was enabled, a timeline switch could cause the walreceiver to try to create the same temporary replication slot again on the same connection. The slot had already been created before the first streaming attempt and still existed, so the second creation attempt failed with a FATAL error such as "could not create replication slot ...". The walreceiver would later be restarted and streaming replication could continue, so this did not permanently break replication. Nevertheless, the unexpected failure is a bug and should be fixed. Fix this by tracking whether the temporary replication slot has already been created for the lifetime of the walreceiver and skipping subsequent creation attempts. Also copy the retained slot name to shared memory on each streaming attempt, since RequestXLogStreaming() clears it when streaming is restarted without a configured primary slot. This also keeps pg_stat_wal_receiver.slot_name populated after timeline switches. Backpatch to all supported versions. Author: ChangAo Chen <cca5507@qq.com> Reviewed-by: Quan Zongliang <quanzongliang@yeah.net> Reviewed-by: Fujii Masao <masao.fujii@gmail.com> Discussion: https://postgr.es/m/tencent_628FDAF814231923BC8E8357BBBC50F94207@qq.com Backpatch-through: 14
-rw-r--r--src/backend/replication/walreceiver.c23
-rw-r--r--src/test/recovery/t/004_timeline_switch.pl46
2 files changed, 61 insertions, 8 deletions
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 220c97d2afa..df9ab04ccb8 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -178,6 +178,7 @@ WalReceiverMain(void)
char *tmp_conninfo;
char slotname[NAMEDATALEN];
bool is_temp_slot;
+ bool temp_slot_created = false;
XLogRecPtr startpoint;
TimeLineID startpointTLI;
TimeLineID primaryTLI;
@@ -438,17 +439,25 @@ WalReceiverMain(void)
WalRcvFetchTimeLineHistoryFiles(startpointTLI, primaryTLI);
/*
- * Create temporary replication slot if requested, and update slot
- * name in shared memory. (Note the slot name cannot already be set
- * in this case.)
+ * Create a temporary replication slot if requested. This only needs
+ * to be done for the first streaming attempt on this connection
+ * because the slot remains available while this connection is reused
+ * for later streaming attempts. Update the slot name in shared
+ * memory each time because RequestXLogStreaming() clears it when
+ * restarting streaming.
*/
if (is_temp_slot)
{
- snprintf(slotname, sizeof(slotname),
- "pg_walreceiver_%lld",
- (long long int) walrcv_get_backend_pid(wrconn));
+ if (!temp_slot_created)
+ {
+ snprintf(slotname, sizeof(slotname),
+ "pg_walreceiver_%lld",
+ (long long int) walrcv_get_backend_pid(wrconn));
+
+ walrcv_create_slot(wrconn, slotname, true, 0, NULL);
- walrcv_create_slot(wrconn, slotname, true, 0, NULL);
+ temp_slot_created = true;
+ }
SpinLockAcquire(&walrcv->mutex);
strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl
index ef994854730..65384fa7948 100644
--- a/src/test/recovery/t/004_timeline_switch.pl
+++ b/src/test/recovery/t/004_timeline_switch.pl
@@ -7,7 +7,7 @@ use warnings;
use File::Path qw(rmtree);
use PostgresNode;
use TestLib;
-use Test::More tests => 6;
+use Test::More tests => 8;
$ENV{PGDATABASE} = 'postgres';
@@ -149,3 +149,47 @@ $node_primary_2->wait_for_catchup($node_standby_3, 'replay',
my $result_2 =
$node_standby_3->safe_psql('postgres', "SELECT count(*) FROM tab_int");
is($result_2, qq(1), 'check content of standby 3');
+
+# Ensure that a WAL receiver creates a temporary replication slot only once
+# when following an upstream across a timeline switch.
+
+# Initialize primary node
+my $node_primary_3 = get_new_node('primary_3');
+$node_primary_3->init(allows_streaming => 1);
+$node_primary_3->start;
+
+# Take backup
+$node_primary_3->backup($backup_name);
+
+# Create standby node
+my $node_standby_4 = get_new_node('standby_4');
+$node_standby_4->init_from_backup($node_primary_3, $backup_name,
+ has_streaming => 1);
+$node_standby_4->append_conf(
+ 'postgresql.conf', qq(
+wal_receiver_create_temp_slot = on
+));
+
+# Restart primary node in standby mode and promote it, switching it
+# to a new timeline.
+$node_primary_3->set_standby_mode;
+$node_primary_3->restart;
+$node_primary_3->promote;
+
+# Start standby node, create some content on primary and check its presence
+# in standby, to ensure that the timeline switch has been done.
+$node_standby_4->start;
+$node_primary_3->safe_psql('postgres',
+ "CREATE TABLE tab_int AS SELECT 1 AS a");
+$node_primary_3->wait_for_catchup($node_standby_4, 'replay',
+ $node_primary_3->lsn('write'));
+
+ok( !$node_standby_4->log_contains(
+ 'could not create replication slot "pg_walreceiver_[0-9]+".*already exists'
+ ),
+ 'temporary replication slot is not recreated across timeline jumps');
+
+my $temp_slot_name = $node_standby_4->safe_psql('postgres',
+ "SELECT slot_name FROM pg_stat_wal_receiver");
+like($temp_slot_name, qr/^pg_walreceiver_[0-9]+$/,
+ 'pg_stat_wal_receiver.slot_name remains set across timeline jumps');