summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFujii Masao2026-08-27 16:15:23 +0000
committerFujii Masao2026-08-27 16:16:04 +0000
commit2bf6144b4e8f806f9a6934a5af3999c65b3504ff (patch)
tree3350a0bb2562e46aec9230949fde216c359d9bad
parent5bf208645b1f989f2b643155d5313f9366b479d1 (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.pl43
2 files changed, 59 insertions, 7 deletions
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 49bf48a3064..e3dfe76cf99 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -162,6 +162,7 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
char *tmp_conninfo;
char slotname[NAMEDATALEN];
bool is_temp_slot;
+ bool temp_slot_created = false;
XLogRecPtr startpoint;
TimeLineID startpointTLI;
TimeLineID primaryTLI;
@@ -426,17 +427,25 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
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, false, false, 0, NULL);
- walrcv_create_slot(wrconn, slotname, true, false, false, 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 d962b3adb1f..a695cd19a86 100644
--- a/src/test/recovery/t/004_timeline_switch.pl
+++ b/src/test/recovery/t/004_timeline_switch.pl
@@ -145,4 +145,47 @@ 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 = PostgreSQL::Test::Cluster->new('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 = PostgreSQL::Test::Cluster->new('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);
+
+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');
+
done_testing();