From c3fe485f91d2efdef09ef1bbc5334a27d27558c2 Mon Sep 17 00:00:00 2001
From: Imran Zaheer <imran.zhir@gmail.com>
Date: Sat, 15 Aug 2026 11:52:16 +0500
Subject: [PATCH v2] Fix checkpointer restartpoint assertion failure.

The checkpointer can try to create a restartpoint while WAL recovery is
still in progress.  During that work it truncates pg_subtrans when
hot_standby is enabled, but that is only safe after recovery has
started pg_subtrans.

Some recovery paths, such as restoring a standalone hot backup without
recovery.signal, can run recovery with EnableHotStandby set but without
initializing hot standby's recovery transaction environment.  In that
case pg_subtrans has not been started when a restartpoint is attempted,
leading to assertion failures while computing the transaction horizon
for TruncateSUBTRANS().

Track in shared recovery state whether pg_subtrans has been initialized
for hot standby during recovery, set it immediately after
StartupSUBTRANS(), and have the checkpointer consult that flag before
truncating pg_subtrans during restartpoints.
---
 src/backend/access/transam/xlog.c         |  7 +++---
 src/backend/access/transam/xlogrecovery.c | 26 +++++++++++++++++++++++
 src/include/access/xlogrecovery.h         |  8 +++++++
 3 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c3baca5193b..b54806a16c3 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6234,6 +6234,7 @@ StartupXLOG(void)
 			 * during recovery and need not be started yet.
 			 */
 			StartupSUBTRANS(oldestActiveXID);
+			SetRecoverySubtransInitialized();
 
 			/*
 			 * If we're beginning at a shutdown checkpoint, we know that
@@ -8363,10 +8364,10 @@ CreateRestartPoint(int flags)
 	 * Truncate pg_subtrans if possible.  We can throw away all data before
 	 * the oldest XMIN of any running transaction.  No future transaction will
 	 * attempt to reference any pg_subtrans entry older than that (see Asserts
-	 * in subtrans.c).  When hot standby is disabled, though, we mustn't do
-	 * this because StartupSUBTRANS hasn't been called yet.
+	 * in subtrans.c).  During recovery, don't truncate pg_subtrans until hot
+	 * standby initialization has started it.
 	 */
-	if (EnableHotStandby)
+	if (RecoverySubtransInitialized())
 		TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());
 
 	/* Real work is done; log and update stats. */
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 6de13b91748..38063b80470 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4451,6 +4451,32 @@ SetPromoteIsTriggered(void)
 	LocalPromoteIsTriggered = true;
 }
 
+/*
+ * Has hot standby initialization started pg_subtrans?
+ */
+bool
+RecoverySubtransInitialized(void)
+{
+	bool		result;
+
+	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
+	result = XLogRecoveryCtl->SharedRecoverySubtransInitialized;
+	SpinLockRelease(&XLogRecoveryCtl->info_lck);
+
+	return result;
+}
+
+/*
+ * Remember that hot standby initialization has started pg_subtrans.
+ */
+void
+SetRecoverySubtransInitialized(void)
+{
+	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
+	XLogRecoveryCtl->SharedRecoverySubtransInitialized = true;
+	SpinLockRelease(&XLogRecoveryCtl->info_lck);
+}
+
 /*
  * Check whether a promote request has arrived.
  */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index a1d8a81dbc1..8786b6d3a8e 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -77,6 +77,12 @@ typedef struct XLogRecoveryCtlData
 	 */
 	bool		SharedPromoteIsTriggered;
 
+	/*
+	 * SharedRecoverySubtransInitialized indicates whether hot standby
+	 * initialization has started pg_subtrans. Protected by info_lck.
+	 */
+	bool		SharedRecoverySubtransInitialized;
+
 	/*
 	 * recoveryWakeupLatch is used to wake up the startup process to continue
 	 * WAL replay, if it is waiting for WAL to arrive or promotion to be
@@ -220,6 +226,8 @@ extern XLogRecPtr GetCurrentReplayRecPtr(TimeLineID *replayEndTLI);
 
 extern bool PromoteIsTriggered(void);
 extern bool CheckPromoteSignal(void);
+extern bool RecoverySubtransInitialized(void);
+extern void SetRecoverySubtransInitialized(void);
 extern void WakeupRecovery(void);
 
 extern void StartupRequestWalReceiverRestart(void);
-- 
2.55.0

