diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c index 1aec50ea5ea..e1189148400 100644 --- a/src/backend/replication/walreceiver.c +++ b/src/backend/replication/walreceiver.c @@ -181,6 +181,7 @@ WalReceiverMain(void) char *tmp_conninfo; char slotname[NAMEDATALEN]; bool is_temp_slot; + bool temp_slot_created = false; XLogRecPtr startpoint; TimeLineID startpointTLI; TimeLineID primaryTLI; @@ -441,17 +442,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, false, 0, NULL); + walrcv_create_slot(wrconn, slotname, true, 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 2f2e8010864..2019d2c6fa3 100644 --- a/src/test/recovery/t/004_timeline_switch.pl +++ b/src/test/recovery/t/004_timeline_switch.pl @@ -148,4 +148,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();