From 98e8199242db0c175f229887a261965cf2161f9c Mon Sep 17 00:00:00 2001 From: ChangAo Chen Date: Tue, 25 Aug 2026 21:31:23 +0800 Subject: [PATCH v3] 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. --- src/backend/replication/walreceiver.c | 23 +++++++---- src/test/recovery/t/004_timeline_switch.pl | 46 +++++++++++++++++++++- 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'); -- 2.55.0