On Sat, Aug 15, 2026 at 9:28 PM Imran Zaheer <[email protected]> wrote: > I agree that tracking whether StartupSUBTRANS() has actually been > called is more direct and robust. I don't have any strong objection to > your approach; your fix looks reasonable to me.
Thanks for the review! I've applied the cosmetic changes to the patch and created patches for the older branches. > My initial concern was more about having too many recovery state > management variables at this point, i.e., ArchiveRecoveryRequested, > InArchiveRecovery, EnableHotStandby, StandbyMode, > StandbyModeRequested, etc. I just wanted to keep the context close to > the existing states and did not want to create a new state for this > specific bug. I agree that adding such new recovery state variables basically would not be a good idea. But, I don't think the flag introduced by this patch falls into that category. It simply tracks whether pg_subtrans has been started during recovery, so I don't have much concern about adding it. Regards, -- Fujii Masao
From 82029dca028c9427a821ef06e27e28198ce1aba6 Mon Sep 17 00:00:00 2001 From: Imran Zaheer <[email protected]> Date: Sat, 15 Aug 2026 23:50:33 +0900 Subject: [PATCH v3] Fix checkpointer restartpoint assertion failure When recovery starts from a backup without a signal file, pg_subtrans is not started at the beginning of recovery and remains unstarted throughout recovery. However, previously, a restartpoint run by the checkpointer during recovery nevertheless tried to truncate pg_subtrans, triggering the assertion failure: TRAP: failed Assert("TransactionIdIsValid(initial)") This commit fixes this by tracking whether pg_subtrans has been started during recovery, and have the checkpointer check this flag before truncating pg_subtrans at restartpoints. Backpatch to all supported versions. --- src/backend/access/transam/xlog.c | 7 ++--- src/backend/access/transam/xlogrecovery.c | 32 +++++++++++++++++++++++ src/include/access/xlogrecovery.h | 2 ++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f79961633a6..e9508fe9d7b 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -5260,6 +5260,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 @@ -7243,10 +7244,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 b07a54a9216..26738af34ba 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -313,6 +313,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 failover trigger file @@ -4492,6 +4498,32 @@ CheckPromoteSignal(void) return false; } +/* + * 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); +} + /* * Wake up startup process to replay newly arrived WAL, or to notice that * failover has been requested. diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h index 0aa85d90e89..55ccbe23f0b 100644 --- a/src/include/access/xlogrecovery.h +++ b/src/include/access/xlogrecovery.h @@ -145,6 +145,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
v3-0001-Fix-checkpointer-restartpoint-assertion-failure.patch
Description: Binary data
From f66e3160652b73637b157df5326bf67f6bc9e19a Mon Sep 17 00:00:00 2001 From: Imran Zaheer <[email protected]> Date: Sun, 16 Aug 2026 00:08:56 +0900 Subject: [PATCH v3] Fix checkpointer restartpoint assertion failure When recovery starts from a backup without a signal file, pg_subtrans is not started at the beginning of recovery and remains unstarted throughout recovery. However, previously, a restartpoint run by the checkpointer during recovery nevertheless tried to truncate pg_subtrans, triggering the assertion failure: TRAP: failed Assert("TransactionIdIsValid(initial)") This commit fixes this by tracking whether pg_subtrans has been started during recovery, and have the checkpointer check this flag before truncating pg_subtrans at restartpoints. Backpatch to all supported versions. --- src/backend/access/transam/xlog.c | 42 ++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index ec84c77ddfa..bb84525e665 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -689,6 +689,12 @@ typedef struct XLogCtlData */ bool SharedPromoteIsTriggered; + /* + * SharedRecoverySubtransInitialized indicates whether hot standby + * initialization has started pg_subtrans. Protected by info_lck. + */ + bool SharedRecoverySubtransInitialized; + /* * WalWriterSleeping indicates whether the WAL writer is currently in * low-power mode (and hence should be nudged if an async commit occurs). @@ -971,6 +977,8 @@ static void ReadControlFile(void); static char *str_time(pg_time_t tnow); static void SetPromoteIsTriggered(void); static bool CheckForStandbyTrigger(void); +static bool RecoverySubtransInitialized(void); +static void SetRecoverySubtransInitialized(void); #ifdef WAL_DEBUG static void xlog_outrec(StringInfo buf, XLogReaderState *record); @@ -5295,6 +5303,7 @@ XLOGShmemInit(void) XLogCtl->SharedHotStandbyActive = false; XLogCtl->InstallXLogFileSegmentActive = false; XLogCtl->SharedPromoteIsTriggered = false; + XLogCtl->SharedRecoverySubtransInitialized = false; XLogCtl->WalWriterSleeping = false; SpinLockInit(&XLogCtl->Insert.insertpos_lck); @@ -7308,6 +7317,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 @@ -10029,10 +10039,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. */ @@ -13408,6 +13418,32 @@ CheckPromoteSignal(void) return false; } +/* + * Has hot standby initialization started pg_subtrans? + */ +static bool +RecoverySubtransInitialized(void) +{ + bool result; + + SpinLockAcquire(&XLogCtl->info_lck); + result = XLogCtl->SharedRecoverySubtransInitialized; + SpinLockRelease(&XLogCtl->info_lck); + + return result; +} + +/* + * Remember that hot standby initialization has started pg_subtrans. + */ +static void +SetRecoverySubtransInitialized(void) +{ + SpinLockAcquire(&XLogCtl->info_lck); + XLogCtl->SharedRecoverySubtransInitialized = true; + SpinLockRelease(&XLogCtl->info_lck); +} + /* * Wake up startup process to replay newly arrived WAL, or to notice that * failover has been requested. -- 2.55.0
From 1ad2b525750aa519735b5c43522741809076904f Mon Sep 17 00:00:00 2001 From: Imran Zaheer <[email protected]> Date: Sat, 15 Aug 2026 23:35:35 +0900 Subject: [PATCH v3] Fix checkpointer restartpoint assertion failure When recovery starts from a backup without a signal file, pg_subtrans is not started at the beginning of recovery and remains unstarted throughout recovery. However, previously, a restartpoint run by the checkpointer during recovery nevertheless tried to truncate pg_subtrans, triggering the assertion failure: TRAP: failed Assert("TransactionIdIsValid(initial)") This commit fixes this by tracking whether pg_subtrans has been started during recovery, and have the checkpointer check this flag before truncating pg_subtrans at restartpoints. Backpatch to all supported versions. --- src/backend/access/transam/xlog.c | 7 ++--- src/backend/access/transam/xlogrecovery.c | 32 +++++++++++++++++++++++ src/include/access/xlogrecovery.h | 2 ++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 2fd06e37999..4165f31f921 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -5844,6 +5844,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 @@ -7856,10 +7857,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 37d6090e465..d94b291495d 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -322,6 +322,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 @@ -4523,6 +4529,32 @@ CheckPromoteSignal(void) return false; } +/* + * 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); +} + /* * Wake up startup process to replay newly arrived WAL, or to notice that * failover has been requested. diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h index 91446303024..771ff1ea23a 100644 --- a/src/include/access/xlogrecovery.h +++ b/src/include/access/xlogrecovery.h @@ -146,6 +146,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
