From 525edd0e35111b19b97d3fd935042be138a328d8 Mon Sep 17 00:00:00 2001
From: Nick Ivanov <nick.ivanov@enterprisedb.com>
Date: Fri, 11 Sep 2026 15:18:03 +0100
Subject: [PATCH v3 2/2] Create pg_basebackup's replication slot before
 starting the backup

A checkpoint can remove the backup's starting WAL segment before
pg_basebackup creates its replication slot.  Creating the slot after
receiving the startpoint is too late to protect that segment.

Create the slot and reserve WAL before sending BASE_BACKUP, keeping the
same connection for the WAL streamer.  This covers both requested
permanent slots and automatically created temporary slots.

Backpatch-through: 15
Discussion: https://www.postgresql.org/message-id/985de9f0-cbb6-4235-a6cd-32242f74e1f3@enterprisedb.com
---
 src/bin/pg_basebackup/pg_basebackup.c | 81 +++++++++++++++------------
 1 file changed, 45 insertions(+), 36 deletions(-)

diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index c3b87a19e76..00db4b6784a 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -613,7 +613,8 @@ LogStreamerMain(logstreamer_param *param)
  * stream the logfile in parallel with the backups.
  */
 static void
-StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier,
+StartLogStreamer(PGconn *walconn, char *startpos, uint32 timeline,
+				 char *sysidentifier,
 				 pg_compress_algorithm wal_compress_algorithm,
 				 int wal_compress_level)
 {
@@ -625,6 +626,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier,
 	param->sysidentifier = sysidentifier;
 	param->wal_compress_algorithm = wal_compress_algorithm;
 	param->wal_compress_level = wal_compress_level;
+	param->bgconn = walconn;
 
 	/* Convert the starting position */
 	if (!pg_parse_lsn(startpos, &param->startptr))
@@ -639,46 +641,12 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier,
 		pg_fatal("could not create pipe for background process: %m");
 #endif
 
-	/* Get a second connection */
-	param->bgconn = GetConnection();
-	if (!param->bgconn)
-		/* Error message already written in GetConnection() */
-		exit(1);
-
 	/* In post-10 cluster, pg_xlog has been renamed to pg_wal */
 	snprintf(param->xlog, sizeof(param->xlog), "%s/%s",
 			 basedir,
 			 PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ?
 			 "pg_xlog" : "pg_wal");
 
-	/* Temporary replication slots are only supported in 10 and newer */
-	if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_TEMP_SLOTS)
-		temp_replication_slot = false;
-
-	/*
-	 * Create replication slot if requested
-	 */
-	if (temp_replication_slot && !replication_slot)
-		replication_slot = psprintf("pg_basebackup_%u",
-									(unsigned int) PQbackendPID(param->bgconn));
-	if (temp_replication_slot || create_slot)
-	{
-		if (!CreateReplicationSlot(param->bgconn, replication_slot, NULL,
-								   temp_replication_slot, true, true, false,
-								   false, false))
-			exit(1);
-
-		if (verbose)
-		{
-			if (temp_replication_slot)
-				pg_log_info("created temporary replication slot \"%s\"",
-							replication_slot);
-			else
-				pg_log_info("created replication slot \"%s\"",
-							replication_slot);
-		}
-	}
-
 	if (format == 'p')
 	{
 		/*
@@ -1754,6 +1722,7 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 	int			writing_to_stdout;
 	bool		use_new_option_syntax = false;
 	PQExpBufferData buf;
+	PGconn	   *walconn = NULL;
 
 	Assert(conn != NULL);
 	initPQExpBuffer(&buf);
@@ -1970,6 +1939,46 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 									  compression_detail);
 	}
 
+	/* If we were asked to stream WAL, create a separate connection for that */
+	if (includewal == STREAM_WAL)
+	{
+		walconn = GetConnection();
+		if (!walconn)
+			/* Error message already written in GetConnection() */
+			exit(1);
+
+		/*
+		 * If we need to create a slot, do it now, before requesting a
+		 * checkpoint, to ensure the WAL we want is not removed until we
+		 * actually start streaming.
+		 */
+
+		/* Temporary replication slots are only supported in 10 and newer */
+		if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_TEMP_SLOTS)
+			temp_replication_slot = false;
+
+		if (temp_replication_slot && !replication_slot)
+			replication_slot = psprintf("pg_basebackup_%u",
+										(unsigned int) PQbackendPID(walconn));
+		if (temp_replication_slot || create_slot)
+		{
+			if (!CreateReplicationSlot(walconn, replication_slot, NULL,
+									   temp_replication_slot, true, true, false,
+									   false, false))
+				exit(1);
+
+			if (verbose)
+			{
+				if (temp_replication_slot)
+					pg_log_info("created temporary replication slot \"%s\"",
+								replication_slot);
+				else
+					pg_log_info("created replication slot \"%s\"",
+								replication_slot);
+			}
+		}
+	}
+
 	if (verbose)
 		pg_log_info("initiating base backup, waiting for checkpoint to complete");
 
@@ -2100,7 +2109,7 @@ BaseBackup(char *compression_algorithm, char *compression_detail,
 			wal_compress_level = 0;
 		}
 
-		StartLogStreamer(xlogstart, starttli, sysidentifier,
+		StartLogStreamer(walconn, xlogstart, starttli, sysidentifier,
 						 wal_compress_algorithm,
 						 wal_compress_level);
 	}
-- 
That's all, folks. May the source be with you.

