From f0089b766261470a7b8c095bb414b053ef9bab10 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Wed, 2 Sep 2026 14:33:25 +0900 Subject: [PATCH] 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. Reported-by: Imran Zaheer Author: Imran Zaheer Author: Fujii Masao Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/CA+UBfamzfEReT0VOGRUW_=_AecCYQ-CNKLR_T4Q+YEmJAH3fdg@mail.gmail.com Backpatch-through: 14 --- 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 d9a2ce5e40d..02e02e3fa8c 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -5410,6 +5410,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 @@ -7425,10 +7426,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 512abcc6ab7..0838d2c686e 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -315,6 +315,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 @@ -4442,6 +4448,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 47c29350f5d..7737f9e2cf0 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);