Hi
While doing some benchmarking related to my work with the recovery
pipelining [1], I found that the checkpointer fails when trying to
take a time-based restartpoint during the crash recovery.
Right now I am facing this assertion failure.
TRAP: failed Assert("TransactionIdIsValid(initial)"), File:
"../../../../../home/imran/Desktop/work/pg/postgres/src/backend/storage/ipc/procarray.c",
Line: 1698, PID: 1279722
postgres: checkpointer (ExceptionalCondition+0x72)[0x5985b7bfb814]
postgres: checkpointer (+0x52ae2a)[0x5985b7a51e2a]
postgres: checkpointer
(GetOldestTransactionIdConsideredRunning+0x24)[0x5985b7a53797]
postgres: checkpointer (CreateRestartPoint+0x5f4)[0x5985b7707611]
postgres: checkpointer (CheckpointerMain+0x653)[0x5985b79a554b]
postgres: checkpointer (postmaster_child_launch+0x124)[0x5985b79a82bb]
postgres: checkpointer (+0x484204)[0x5985b79ab204]
postgres: checkpointer (PostmasterMain+0x130a)[0x5985b79aeaaf]
postgres: checkpointer (main+0x1e4)[0x5985b78c10b5]
/lib/x86_64-linux-gnu/libc.so.6(+0x29d90)[0x78c058829d90]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80)[0x78c058829e40]
postgres: checkpointer (_start+0x25)[0x5985b7616065]
It looks like the checkpointer is not aware of whether hot standby
initialization was done during startup, which happens only in the case
of ArchiveRecoveryRequested [2]. In this specific case, the
checkpointer assumes hot standby initialization would have been done,
so it tries to call TruncateSUBTRANS(). A simple fix is to add
ArchiveRecoveryRequested to recovery shared memory XLogRecoveryCtl;
then the checkpointer will simply skip TruncateSUBTRANS if archive
recovery was not requested. I have attached my patch below.
Repro:
I had a script for my benchmarking work, but I changed it to reproduce
this specific bug [3].
Before running the script, set up the env file in `config/env.conf`.
You may need to increase WORKLOAD_DURATION so that crash recovery can
run long enough to trigger a restart point.
```
./run_test.sh -i # basebackup & archiving
./run_test.sh # cpy archive to pg_wal & run crash recovery
```
The script copies the archived wal to the basebackup pg_wal and then
starts the cluster. This will make the cluster undergo a crash
recovery. You will see the failing assertion in the log file created
`recoverylog`.
Looks like some work was already done trying to fix a similar issue in
the past [4],[5].
[1]:
https://www.postgresql.org/message-id/CA%2BUBfa%3DvDV8wbmAV0pgrx-FuJh%2Bx8YOW23vJ90Jzr%3D14rV%2B9jA%40mail.gmail.com
[2]:
https://github.com/postgres/postgres/blob/b59783502224c0ae721974a5d9b6fb915cbd37e1/src/backend/access/transam/xlog.c#L6212
[3]:
https://github.com/imranzaheer612/pg-recovery-testing/tree/restartpoint-fail
[4]:
https://www.postgresql.org/message-id/flat/17744-2c95e2b7783d7232%40postgresql.org
[5]:
https://www.postgresql.org/message-id/flat/18119-5f60199d6207f4d1%40postgresql.org
Thanks,
Imran Zaheer
From 0fc95289424b7a66c875dc8a3de0cec5cbedfd3a Mon Sep 17 00:00:00 2001
From: Imran Zaheer <[email protected]>
Date: Sat, 15 Aug 2026 11:52:16 +0500
Subject: [PATCH v1] Fix checkpointer restartpoint assertion failure.
The checkpointer fails with an assertion failure when it tries to
take a restartpoint during a crash recovery. This happens because the
checkpointer is not aware of whether an archive recovery was
requested or not, and whether hot standby initialization was done.
To fix this we track ArchiveRecoveryRequested in XLogRecoveryCtl so the
checkpointer can determine whether hot standby initialization was
performed and skip TruncateSUBTRANS() when archive recovery was not
requested.
---
src/backend/access/transam/xlog.c | 2 +-
src/backend/access/transam/xlogrecovery.c | 32 +++++++++++++++++++++++
src/include/access/xlogrecovery.h | 7 +++++
3 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c3baca5193b..5287b475195 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -8366,7 +8366,7 @@ CreateRestartPoint(int flags)
* in subtrans.c). When hot standby is disabled, though, we mustn't do
* this because StartupSUBTRANS hasn't been called yet.
*/
- if (EnableHotStandby)
+ if (EnableHotStandby && ArchiveRecoveryWasRequested())
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..1904d91e3ed 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -394,6 +394,7 @@ static bool HotStandbyActiveInReplay(void);
static void SetCurrentChunkStartTime(TimestampTz xtime);
static void SetLatestXTime(TimestampTz xtime);
static RecoveryTargetType DetermineRecoveryTargetType(void);
+static void SetArchiveRecoveryWasRequested(void);
/*
* Register shared memory for WAL recovery
@@ -982,6 +983,9 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
*wasShutdown_ptr = wasShutdown;
*haveBackupLabel_ptr = haveBackupLabel;
*haveTblspcMap_ptr = haveTblspcMap;
+
+ if (ArchiveRecoveryRequested)
+ SetArchiveRecoveryWasRequested();
}
/*
@@ -4451,6 +4455,34 @@ SetPromoteIsTriggered(void)
LocalPromoteIsTriggered = true;
}
+bool
+ArchiveRecoveryWasRequested(void)
+{
+ /*
+ * We only set local ArchiveRecoveryRequested once in the startup while
+ * reading recovery signal files. So there's no need to keep checking after
+ * the shared variable has once been seen true.
+ */
+ if (ArchiveRecoveryRequested)
+ return true;
+
+ SpinLockAcquire(&XLogRecoveryCtl->info_lck);
+ ArchiveRecoveryRequested = XLogRecoveryCtl->SharedArchiveRecoveryRequested;
+ SpinLockRelease(&XLogRecoveryCtl->info_lck);
+
+ return ArchiveRecoveryRequested;
+}
+
+void
+SetArchiveRecoveryWasRequested(void)
+{
+ SpinLockAcquire(&XLogRecoveryCtl->info_lck);
+ XLogRecoveryCtl->SharedArchiveRecoveryRequested = true;
+ SpinLockRelease(&XLogRecoveryCtl->info_lck);
+
+ ArchiveRecoveryRequested = true;
+}
+
/*
* Check whether a promote request has arrived.
*/
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index a1d8a81dbc1..1e0b455506e 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -77,6 +77,12 @@ typedef struct XLogRecoveryCtlData
*/
bool SharedPromoteIsTriggered;
+ /*
+ * SharedArchiveRecoveryRequested indicates if the ArchiveRecoveryRequested
+ * was set during startup InitWalRecovery(). Protected by info_lck.
+ */
+ bool SharedArchiveRecoveryRequested;
+
/*
* 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,7 @@ extern XLogRecPtr GetCurrentReplayRecPtr(TimeLineID *replayEndTLI);
extern bool PromoteIsTriggered(void);
extern bool CheckPromoteSignal(void);
+extern bool ArchiveRecoveryWasRequested(void);
extern void WakeupRecovery(void);
extern void StartupRequestWalReceiverRestart(void);
--
2.34.1