From fd744df4a49dd5b1b7f14113458a546f562ce486 Mon Sep 17 00:00:00 2001 From: ChangAo Chen Date: Tue, 25 Aug 2026 21:31:23 +0800 Subject: [PATCH v2] Fix temporary WAL receiver slot handling on timeline switches. When wal_receiver_create_temp_slot is enabled, a WAL receiver creates a temporary replication slot before starting streaming. If it reaches the end of an old timeline and is instructed to continue on a newer one, it reuses the same connection but enters the streaming setup again. Since is_temp_slot remains true, it attempts to create the same temporary slot again and fails because the slot already exists. Track whether the temporary slot has already been created for the lifetime of the WAL receiver and skip subsequent creation attempts. Continue to copy the locally retained slot name to shared memory on each streaming attempt, since RequestXLogStreaming() clears it when primary_slot_name is not configured. This also keeps pg_stat_wal_receiver.slot_name populated after a timeline switch. Add coverage for both behaviors to the timeline-switch recovery test. --- src/backend/replication/walreceiver.c | 22 +++++++---- src/test/recovery/t/004_timeline_switch.pl | 43 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c index 61dc6a5588b..26271bacd9d 100644 --- a/src/backend/replication/walreceiver.c +++ b/src/backend/replication/walreceiver.c @@ -158,6 +158,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; @@ -431,17 +432,24 @@ 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 stream because the slot remains available + * while this connection is reused for subsequent timelines. Update + * the slot name in shared memory each time because + * RequestXLogStreaming() clears it when restarting on a new timeline. */ 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 f9955b2b4eb..2301aa9e5d0 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', "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(); -- 2.53.0