Subject: Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

From: Michael Paquier <[email-masked]>
Date: Wed, 8 May 2024 13:24:23 +0900

On Mon, May 06, 2024 at 06:55:46PM +0300, [email-masked] wrote:
> as a first step I have introduced the `SharedRecoveryDataFlags` bitmask
> instead of three boolean SharedHotStandbyActive, SharedPromoteIsTriggered
> and SharedStandbyModeRequested flags (the last one from my previous patch)
> and made minimal updates in corresponding code based on that change.

Thanks for the patch.

 /*
- * Local copy of SharedHotStandbyActive variable. False actually means "not
+ * Local copy of XLR_HOT_STANDBY_ACTIVE flag. False actually means "not
  * known, need to check the shared state".
  */
 static bool LocalHotStandbyActive = false;
 
 /*
- * Local copy of SharedPromoteIsTriggered variable. False actually means "not
+ * Local copy of XLR_PROMOTE_IS_TRIGGERED flag. False actually means "not
  * known, need to check the shared state".
  */
 static bool LocalPromoteIsTriggered = false;

It's a bit strange to have a bitwise set of flags in shmem while we
keep these local copies as booleans.  Perhaps it would be cleaner to
merge both local variables into a single bits32 store?

+   uint32      SharedRecoveryDataFlags;

I'd switch to bits32 for flags here.

+bool
+StandbyModeIsRequested(void)
+{
+	/*
+	 * Spinlock is not needed here because XLR_STANDBY_MODE_REQUESTED flag
+	 * can only be read after startup process is done.
+	 */
+	return (XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_STANDBY_MODE_REQUESTED) != 0;
+}

How about introducing a single wrapper function that returns the whole
value SharedRecoveryDataFlags, with the flags published in a header?
Sure, XLR_HOT_STANDBY_ACTIVE is not really exciting because being able
to query a standby implies it, but XLR_PROMOTE_IS_TRIGGERED could be
interesting?  Then this could be used with a function that returns a
text[] array with all the states retrieved?

The refactoring pieces and the function pieces should be split, for
clarity.
--
Michael

---

From: [email-masked]
Date: Thu, 13 Jun 2024 21:07:42 +0300

Hi!

Michael,
I have fixed the patches according to your comments.

> merge both local variables into a single bits32 store?
This is done in v3-0001-Standby-mode-requested.patch

> Then this could be used with a function that returns a
Placed this in the v3-0002-Text-array-sql-wrapper.patch

> The refactoring pieces and the function pieces should be split, for
Sure. I also added the third patch with some tests. Perhaps it would be 
usefull.

Respectfully,

Mikhail Litsarev
Postgres Professional: https://postgrespro.com
From cd32de9ec14dba0011f8c05e6a8ab7e4f7be045c Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <[email-masked]>
Date: Thu, 13 Jun 2024 18:47:47 +0300
Subject: [PATCH] Tests for standby-is-requested mode.

Add tiny tests in src/test/recovery/t/004_timeline_switch.pl
They validate:
 - master is not a replica
 - standby_1 is a replica
 - promoted replica in master mode
 - standby_2 remains a replica after switch to promoted master
---
 src/test/recovery/t/004_timeline_switch.pl | 25 ++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl
index 684838cbab7..11f0b22ae90 100644
--- a/src/test/recovery/t/004_timeline_switch.pl
+++ b/src/test/recovery/t/004_timeline_switch.pl
@@ -16,6 +16,11 @@ my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
 $node_primary->start;
 
+# Validate pg_get_recovery_flags() for master
+my $ret_mode_primary = $node_primary->safe_psql('postgres',
+	'SELECT pg_get_recovery_flags()');
+is($ret_mode_primary, '{}', "master is not a replica");
+
 # Take backup
 my $backup_name = 'my_backup';
 $node_primary->backup($backup_name);
@@ -30,6 +35,12 @@ $node_standby_2->init_from_backup($node_primary, $backup_name,
 	has_streaming => 1);
 $node_standby_2->start;
 
+# Validate standby-mode-requested state for replica node
+my $ret_mode_standby_1 = $node_standby_1->safe_psql('postgres',
+	'SELECT pg_get_recovery_flags()');
+is($ret_mode_standby_1, '{STANDBY_MODE_REQUESTED}', "node_standby_1 is a replica");
+print($ret_mode_standby_1);
+
 # Create some content on primary
 $node_primary->safe_psql('postgres',
 	"CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
@@ -48,6 +59,15 @@ $node_standby_1->psql(
 	stdout => \$psql_out);
 is($psql_out, 't', "promotion of standby with pg_promote");
 
+# Validate pg_get_recovery_flags() for master promoted from standby node.
+# STANDBY_MODE_REQUESTED is returned because standby.signal file
+# was found while being a replica.
+# Use it with pg_is_in_recovery() (will return false), for such use-cases.
+my $ret_mode_1 = $node_standby_1->safe_psql('postgres',
+	'SELECT pg_get_recovery_flags()');
+is($ret_mode_1, '{PROMOTE_IS_TRIGGERED,STANDBY_MODE_REQUESTED}',
+	"node_standby_1 becomes a master");
+
 # Switch standby 2 to replay from standby 1
 my $connstr_1 = $node_standby_1->connstr;
 $node_standby_2->append_conf(
@@ -56,6 +76,11 @@ primary_conninfo='$connstr_1'
 ));
 $node_standby_2->restart;
 
+# Validate STANDBY_MODE_REQUESTED for second replica after restart
+my $ret_mode_standby_2 = $node_standby_2->safe_psql('postgres',
+	'SELECT pg_get_recovery_flags()');
+is($ret_mode_standby_2, '{STANDBY_MODE_REQUESTED}', "node_standby_2 remains a replica");
+
 # Insert some data in standby 1 and check its presence in standby 2
 # to ensure that the timeline switch has been done.
 $node_standby_1->safe_psql('postgres',
-- 
2.34.1

From ac304f75d5f6f9bb9712ef4113da035ad74b7344 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <[email-masked]>
Date: Thu, 13 Jun 2024 18:37:13 +0300
Subject: [PATCH] Wrapper function to extract whole text array from the
 SharedRecoveryDataFlags.

---
 src/backend/access/transam/xlogfuncs.c    | 31 +++++++++++++++++++++++
 src/backend/access/transam/xlogrecovery.c | 17 +++++++++++++
 src/include/access/xlogrecovery.h         |  1 +
 src/include/catalog/pg_proc.dat           |  4 +++
 4 files changed, 53 insertions(+)

diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 4e46baaebdf..9e752639aa4 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -29,6 +29,7 @@
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
 #include "storage/standby.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
@@ -747,3 +748,33 @@ pg_promote(PG_FUNCTION_ARGS)
 						   wait_seconds)));
 	PG_RETURN_BOOL(false);
 }
+
+Datum
+pg_get_recovery_flags(PG_FUNCTION_ARGS)
+{
+/*
+ * Currently supported number of recovery flags is equal to 2:
+ * {XLR_PROMOTE_IS_TRIGGERED, XLR_STANDBY_MODE_REQUESTED}.
+ * It can be extended in future.
+ */
+#define MAX_RECOVERY_FLAGS 2
+
+	bits32		recovery_flags;
+	int			cnt = 0;
+	Datum		flags[MAX_RECOVERY_FLAGS];
+	ArrayType	*txt_arr;
+
+	recovery_flags = GetXLogRecoveryFlags();
+
+	if (recovery_flags & XLR_PROMOTE_IS_TRIGGERED)
+		flags[cnt++] = CStringGetTextDatum("PROMOTE_IS_TRIGGERED");
+
+	if (recovery_flags & XLR_STANDBY_MODE_REQUESTED)
+		flags[cnt++] = CStringGetTextDatum("STANDBY_MODE_REQUESTED");
+
+	Assert(cnt <= MAX_RECOVERY_FLAGS);
+
+	/* Returns bit array as Datum */
+	txt_arr = construct_array_builtin(flags, cnt, TEXTOID);
+	PG_RETURN_ARRAYTYPE_P(txt_arr);
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index de92f48e4ae..bd919732a56 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4399,6 +4399,22 @@ StartupRequestWalReceiverRestart(void)
 	}
 }
 
+/*
+ * Return flags for recovery states.
+ *
+ * Please, see description of
+ * XLR_PROMOTE_IS_TRIGGERED and XLR_STANDBY_MODE_REQUESTED.
+ */
+bits32 GetXLogRecoveryFlags(void)
+{
+	bits32		flags;
+
+	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
+	flags = XLogRecoveryCtl->SharedRecoveryDataFlags;
+	SpinLockRelease(&XLogRecoveryCtl->info_lck);
+
+	return flags;
+}
 
 /*
  * Has a standby promotion already been triggered?
@@ -4551,6 +4567,7 @@ HotStandbyActiveInReplay(void)
 	return (LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE);
 }
 
+
 /*
  * Get latest redo apply position.
  *
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 2cb56fc365f..40487de1846 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -165,6 +165,7 @@ extern TimestampTz GetLatestXTime(void);
 extern TimestampTz GetCurrentChunkReplayStartTime(void);
 extern XLogRecPtr GetCurrentReplayRecPtr(TimeLineID *replayEndTLI);
 
+extern bits32 GetXLogRecoveryFlags(void);
 extern bool PromoteIsTriggered(void);
 extern bool CheckPromoteSignal(void);
 extern void WakeupRecovery(void);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6a5476d3c4c..7b199780c01 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6520,6 +6520,10 @@
 { oid => '3810', descr => 'true if server is in recovery',
   proname => 'pg_is_in_recovery', provolatile => 'v', prorettype => 'bool',
   proargtypes => '', prosrc => 'pg_is_in_recovery' },
+{ oid => '8439',
+  descr => 'return flags for recovery states',
+  proname => 'pg_get_recovery_flags', provolatile => 'v', prorettype => '_text',
+  proargtypes => '', prosrc => 'pg_get_recovery_flags' },
 
 { oid => '3820', descr => 'current wal flush location',
   proname => 'pg_last_wal_receive_lsn', provolatile => 'v',
-- 
2.34.1

From 23e23dd4c0b1c4c69021a4f2bf95586f39d8bd1f Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <[email-masked]>
Date: Mon, 6 May 2024 15:23:52 +0300
Subject: [PATCH] Replace recovery boolean flags with a bits32 set.

Replace local and shared-memory flags (Local/Shared)HotStandbyActive,
(Local/Shared)PromoteIsTriggered with corresponding
(Local/Shared)RecoveryDataFlags.

Introduce XLR_STANDBY_MODE_REQUESTED state which indicates
that a node is in a standby mode at start, while recovery mode is on
(standby.signal file was found at start up).
---
 src/backend/access/transam/xlogrecovery.c | 89 ++++++++++++++---------
 src/include/access/xlogrecovery.h         | 21 ++++++
 2 files changed, 76 insertions(+), 34 deletions(-)

diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index b45b8331720..de92f48e4ae 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -170,16 +170,22 @@ static XLogRecPtr RedoStartLSN = InvalidXLogRecPtr;
 static TimeLineID RedoStartTLI = 0;
 
 /*
- * Local copy of SharedHotStandbyActive variable. False actually means "not
- * known, need to check the shared state".
- */
-static bool LocalHotStandbyActive = false;
-
-/*
- * Local copy of SharedPromoteIsTriggered variable. False actually means "not
- * known, need to check the shared state".
+ * This bit array is introduced to keep the local copies of RecoveryDataFlags
+ * in corresponding SharedRecoveryDataFlags bitset.
+ *
+ * Currently it provides the following states:
+ *
+ *	--	Local copy of XLR_HOT_STANDBY_ACTIVE flag. False actually means "not
+ *		known, need to check the shared state.
+ *
+ *	--	Local copy of XLR_PROMOTE_IS_TRIGGERED flag. False actually means "not
+ *		known, need to check the shared state".
+ *
+ * 	--	XLR_STANDBY_MODE_REQUESTED is not mirrored here.
+ *
+ * This bitset can be extended in future.
  */
-static bool LocalPromoteIsTriggered = false;
+static bits32 LocalRecoveryDataFlags;
 
 /* Has the recovery code requested a walreceiver wakeup? */
 static bool doRequestWalReceiverReply;
@@ -297,23 +303,26 @@ bool		reachedConsistency = false;
 static char *replay_image_masked = NULL;
 static char *primary_image_masked = NULL;
 
-
 /*
  * Shared-memory state for WAL recovery.
  */
 typedef struct XLogRecoveryCtlData
 {
 	/*
-	 * SharedHotStandbyActive indicates if we allow hot standby queries to be
-	 * run.  Protected by info_lck.
-	 */
-	bool		SharedHotStandbyActive;
-
-	/*
-	 * SharedPromoteIsTriggered indicates if a standby promotion has been
-	 * triggered.  Protected by info_lck.
+	 * This bit array is introduced to keep the following states:
+	 *
+	 *	--	XLR_HOT_STANDBY_ACTIVE indicates if we allow hot standby queries
+	 *		to be run. Protected by info_lck.
+	 *
+	 *	--	XLR_PROMOTE_IS_TRIGGERED indicates if a standby promotion
+	 *		has been triggered. Protected by info_lck.
+	 *
+	 * 	--	XLR_STANDBY_MODE_REQUESTED indicates if we're in a standby mode
+	 *		at start, while recovery mode is on. No info_lck protection.
+	 *
+	 *	and can be extended in future.
 	 */
-	bool		SharedPromoteIsTriggered;
+	bits32		SharedRecoveryDataFlags;
 
 	/*
 	 * recoveryWakeupLatch is used to wake up the startup process to continue
@@ -1082,10 +1091,17 @@ readRecoverySignalFile(void)
 
 	StandbyModeRequested = false;
 	ArchiveRecoveryRequested = false;
+	/*
+	 * There is no need to use Spinlock here because only the startup
+	 * process modifies the SharedRecoveryDataFlags bit here and
+	 * no other processes are reading it at that time.
+	 */
+	XLogRecoveryCtl->SharedRecoveryDataFlags &= ~XLR_STANDBY_MODE_REQUESTED;
 	if (standby_signal_file_found)
 	{
 		StandbyModeRequested = true;
 		ArchiveRecoveryRequested = true;
+		XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_STANDBY_MODE_REQUESTED;
 	}
 	else if (recovery_signal_file_found)
 	{
@@ -2254,15 +2270,15 @@ CheckRecoveryConsistency(void)
 	 * enabling connections.
 	 */
 	if (standbyState == STANDBY_SNAPSHOT_READY &&
-		!LocalHotStandbyActive &&
+		!(LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE) &&
 		reachedConsistency &&
 		IsUnderPostmaster)
 	{
 		SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-		XLogRecoveryCtl->SharedHotStandbyActive = true;
+		XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_HOT_STANDBY_ACTIVE;
 		SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
-		LocalHotStandbyActive = true;
+		LocalRecoveryDataFlags |= XLR_HOT_STANDBY_ACTIVE;
 
 		SendPostmasterSignal(PMSIGNAL_BEGIN_HOT_STANDBY);
 	}
@@ -2925,11 +2941,11 @@ static void
 recoveryPausesHere(bool endOfRecovery)
 {
 	/* Don't pause unless users can connect! */
-	if (!LocalHotStandbyActive)
+	if (!(LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE))
 		return;
 
 	/* Don't pause after standby promotion has been triggered */
-	if (LocalPromoteIsTriggered)
+	if (LocalRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED)
 		return;
 
 	if (endOfRecovery)
@@ -4398,21 +4414,23 @@ PromoteIsTriggered(void)
 	 * triggered. We can't trigger a promotion again, so there's no need to
 	 * keep checking after the shared variable has once been seen true.
 	 */
-	if (LocalPromoteIsTriggered)
+	if (LocalRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED)
 		return true;
 
 	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-	LocalPromoteIsTriggered = XLogRecoveryCtl->SharedPromoteIsTriggered;
+	LocalRecoveryDataFlags &= ~XLR_PROMOTE_IS_TRIGGERED;
+	LocalRecoveryDataFlags |=
+		(XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED);
 	SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
-	return LocalPromoteIsTriggered;
+	return (LocalRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED);
 }
 
 static void
 SetPromoteIsTriggered(void)
 {
 	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-	XLogRecoveryCtl->SharedPromoteIsTriggered = true;
+	XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_PROMOTE_IS_TRIGGERED;
 	SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
 	/*
@@ -4423,7 +4441,7 @@ SetPromoteIsTriggered(void)
 	 */
 	SetRecoveryPause(false);
 
-	LocalPromoteIsTriggered = true;
+	LocalRecoveryDataFlags |= XLR_PROMOTE_IS_TRIGGERED;
 }
 
 /*
@@ -4432,7 +4450,7 @@ SetPromoteIsTriggered(void)
 static bool
 CheckForStandbyTrigger(void)
 {
-	if (LocalPromoteIsTriggered)
+	if (LocalRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED)
 		return true;
 
 	if (IsPromoteSignaled() && CheckPromoteSignal())
@@ -4506,16 +4524,18 @@ HotStandbyActive(void)
 	 * can't de-activate Hot Standby, so there's no need to keep checking
 	 * after the shared variable has once been seen true.
 	 */
-	if (LocalHotStandbyActive)
+	if (LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE)
 		return true;
 	else
 	{
 		/* spinlock is essential on machines with weak memory ordering! */
 		SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-		LocalHotStandbyActive = XLogRecoveryCtl->SharedHotStandbyActive;
+		LocalRecoveryDataFlags &= ~XLR_HOT_STANDBY_ACTIVE;
+		LocalRecoveryDataFlags |=
+			(XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE);
 		SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
-		return LocalHotStandbyActive;
+		return LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE;
 	}
 }
 
@@ -4527,7 +4547,8 @@ static bool
 HotStandbyActiveInReplay(void)
 {
 	Assert(AmStartupProcess() || !IsPostmasterEnvironment);
-	return LocalHotStandbyActive;
+
+	return (LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE);
 }
 
 /*
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index c423464e8bc..2cb56fc365f 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -16,6 +16,27 @@
 #include "lib/stringinfo.h"
 #include "utils/timestamp.h"
 
+/*
+ * The flag indicates if we allow hot standby queries to be run.
+ * Protected by info_lck.
+ */
+#define	XLR_HOT_STANDBY_ACTIVE		0x01
+/*
+ * The flag indicates if a standby promotion has been triggered.
+ * Protected by info_lck.
+ */
+#define	XLR_PROMOTE_IS_TRIGGERED	0x02
+/*
+ * The flag indicates if we're in a standby mode at start,
+ * while recovery mode is on.
+ * It is introduced to distinguish a replica from a regular instance
+ * in a Point In Time Recovery (PITR) mode.
+ *
+ * It is selected	if standby.signal file is found at startup process
+ * It is skipped	otherwise.
+ */
+#define	XLR_STANDBY_MODE_REQUESTED	0x04
+
 /*
  * Recovery target type.
  * Only set during a Point in Time recovery, not when in standby mode.
-- 
2.34.1

---

From: Michael Paquier <[email-masked]>
Date: Fri, 14 Jun 2024 13:51:17 +0900

This comment is incorrect for XLR_STANDBY_MODE_REQUESTED?  A startup
we would unlikely be in standby mode, most likely in crash recovery,
then switch to standby mode.

-    LocalPromoteIsTriggered = XLogRecoveryCtl->SharedPromoteIsTriggered;
+    LocalRecoveryDataFlags &= ~XLR_PROMOTE_IS_TRIGGERED;
+    LocalRecoveryDataFlags |=
+        (XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED)

Are these complications really needed?  All these flags are false,
then switched to true.  true -> false is not possible.

Shouldn't STANDBY_MODE be only used in the local flag, as well as an
ARCHIVE_RECOVERY_REQUESTED?  It looks like this could push a bit more
forward the removal of more of these booleans, with a bit more work..

+
 /*
Some noise lying around.

Yep, that's the correct way to do it.

The test additions are welcome.  Note that we avoid the word "master",
see 229f8c219f8f.
--
Michael

---

From: [email-masked]
Date: Fri, 10 Jan 2025 23:39:07 +0300

Hi!

Michael, sorry for such a long time to deliver next version of the patch 
from my side.
In this version I have fixed all your propositions, hopefully correct.

There is one point that I would like to emphasize, namely
> forward the removal of more of these booleans, with a bit more work..
I made corresponding changes, but given that these three variables
bool ArchiveRecoveryRequested;
bool InArchiveRecovery;
bool StandbyMode;
are used in other units, and (if I understand correctly) we decided to 
move them in one localRecoveryFlags bitset too,
I changed their extern calls to boolean functions calls instead of 
extern variables.

> The test additions are welcome.
My test additions were based on copy of StandbyModeRequested placed in 
shared memory
even the startup process is done. With current implementation they seems 
to be not very usefull now.

Respectfully,

Mikhail Litsarev,

From eaf661c022f82217d33cfdb3776c69675f82ac67 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <[email-masked]>
Date: Fri, 10 Jan 2025 21:23:02 +0300
Subject: [PATCH] Replace recovery boolean flags with a bits32 set.

Move local booleans ArchiveRecoveryRequested, InArchiveRecovery,
StandbyModeRequested, StandbyMode, LocalHotStandbyActive,
LocalPromoteIsTriggered into localRecoveryFlags bitset.

Move SharedHotStandbyActive, SharedPromoteIsTriggered members of
XLogRecoveryCtlData into sharedRecoveryFlags bitset.

Refactor code according to the changes.
---
 src/backend/access/transam/timeline.c      |   6 +-
 src/backend/access/transam/xlog.c          |  26 +--
 src/backend/access/transam/xlogarchive.c   |   4 +-
 src/backend/access/transam/xlogrecovery.c  | 221 ++++++++++-----------
 src/backend/replication/logical/slotsync.c |   2 +-
 src/include/access/xlog_internal.h         |   7 +-
 src/include/access/xlogrecovery.h          |  34 +++-
 7 files changed, 161 insertions(+), 139 deletions(-)

diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index a27f27cc037..c9f53c4b667 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -93,7 +93,7 @@ readTimeLineHistory(TimeLineID targetTLI)
 		return list_make1(entry);
 	}
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		TLHistoryFileName(histfname, targetTLI);
 		fromArchive =
@@ -229,7 +229,7 @@ existsTimeLineHistory(TimeLineID probeTLI)
 	if (probeTLI == 1)
 		return false;
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		TLHistoryFileName(histfname, probeTLI);
 		RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
@@ -331,7 +331,7 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
 	/*
 	 * If a history file exists for the parent, copy it verbatim
 	 */
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		TLHistoryFileName(histfname, parentTLI);
 		RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index bf3dbda901d..2d6615d94d0 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -5388,7 +5388,7 @@ CheckRequiredParameterValues(void)
 	 * For archive recovery, the WAL must be generated with at least 'replica'
 	 * wal_level.
 	 */
-	if (ArchiveRecoveryRequested && ControlFile->wal_level == WAL_LEVEL_MINIMAL)
+	if (ArchiveRecoveryRequested() && ControlFile->wal_level == WAL_LEVEL_MINIMAL)
 	{
 		ereport(FATAL,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
@@ -5401,7 +5401,7 @@ CheckRequiredParameterValues(void)
 	 * For Hot Standby, the WAL must be generated with 'replica' mode, and we
 	 * must have at least as many backend slots as the primary.
 	 */
-	if (ArchiveRecoveryRequested && EnableHotStandby)
+	if (ArchiveRecoveryRequested() && EnableHotStandby)
 	{
 		/* We ignore autovacuum_worker_slots when we make this test. */
 		RecoveryRequiresIntParameter("max_connections",
@@ -5561,8 +5561,8 @@ StartupXLOG(void)
 	 *
 	 * InitWalRecovery analyzes the control file and the backup label file, if
 	 * any.  It updates the in-memory ControlFile buffer according to the
-	 * starting checkpoint, and sets InRecovery and ArchiveRecoveryRequested.
-	 * It also applies the tablespace map file, if any.
+	 * starting checkpoint, and sets SX_ARCHIVE_RECOVERY_REQUESTED and
+	 * InRecovery. It also applies the tablespace map file, if any.
 	 */
 	InitWalRecovery(ControlFile, &wasShutdown,
 					&haveBackupLabel, &haveTblspcMap);
@@ -5694,7 +5694,7 @@ StartupXLOG(void)
 	{
 		/* Initialize state for RecoveryInProgress() */
 		SpinLockAcquire(&XLogCtl->info_lck);
-		if (InArchiveRecovery)
+		if (InArchiveRecovery())
 			XLogCtl->SharedRecoveryState = RECOVERY_STATE_ARCHIVE;
 		else
 			XLogCtl->SharedRecoveryState = RECOVERY_STATE_CRASH;
@@ -5747,7 +5747,7 @@ StartupXLOG(void)
 		 * startup process to think that there are still invalid page
 		 * references when checking for data consistency.
 		 */
-		if (InArchiveRecovery)
+		if (InArchiveRecovery())
 		{
 			LocalMinRecoveryPoint = ControlFile->minRecoveryPoint;
 			LocalMinRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
@@ -5781,7 +5781,7 @@ StartupXLOG(void)
 		 * control file and we've established a recovery snapshot from a
 		 * running-xacts WAL record.
 		 */
-		if (ArchiveRecoveryRequested && EnableHotStandby)
+		if (ArchiveRecoveryRequested() && EnableHotStandby)
 		{
 			TransactionId *xids;
 			int			nxids;
@@ -5894,7 +5894,7 @@ StartupXLOG(void)
 		 * recover from an online backup but never called pg_backup_stop(), or
 		 * you didn't archive all the WAL needed.
 		 */
-		if (ArchiveRecoveryRequested || ControlFile->backupEndRequired)
+		if (ArchiveRecoveryRequested() || ControlFile->backupEndRequired)
 		{
 			if (!XLogRecPtrIsInvalid(ControlFile->backupStartPoint) || ControlFile->backupEndRequired)
 				ereport(FATAL,
@@ -5946,7 +5946,7 @@ StartupXLOG(void)
 	 * In a normal crash recovery, we can just extend the timeline we were in.
 	 */
 	newTLI = endOfRecoveryInfo->lastRecTLI;
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		newTLI = findNewestTimeLine(recoveryTargetTLI) + 1;
 		ereport(LOG,
@@ -6139,7 +6139,7 @@ StartupXLOG(void)
 	XLogReportParameters();
 
 	/* If this is archive recovery, perform post-recovery cleanup actions. */
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog, newTLI);
 
 	/*
@@ -6298,7 +6298,7 @@ PerformRecoveryXLogAction(void)
 	 * of a full checkpoint. A checkpoint is requested later, after we're
 	 * fully out of recovery mode and already accepting queries.
 	 */
-	if (ArchiveRecoveryRequested && IsUnderPostmaster &&
+	if (ArchiveRecoveryRequested() && IsUnderPostmaster &&
 		PromoteIsTriggered())
 	{
 		promoted = true;
@@ -8292,7 +8292,7 @@ xlog_redo(XLogReaderState *record)
 		 * record, the backup was canceled and the end-of-backup record will
 		 * never arrive.
 		 */
-		if (ArchiveRecoveryRequested &&
+		if (ArchiveRecoveryRequested() &&
 			!XLogRecPtrIsInvalid(ControlFile->backupStartPoint) &&
 			XLogRecPtrIsInvalid(ControlFile->backupEndPoint))
 			ereport(PANIC,
@@ -8533,7 +8533,7 @@ xlog_redo(XLogReaderState *record)
 		 * local copies cannot be updated as long as crash recovery is
 		 * happening and we expect all the WAL to be replayed.
 		 */
-		if (InArchiveRecovery)
+		if (InArchiveRecovery())
 		{
 			LocalMinRecoveryPoint = ControlFile->minRecoveryPoint;
 			LocalMinRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 1ef1713c91a..ad379acc30a 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -68,7 +68,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 	 * Ignore restore_command when not in archive recovery (meaning we are in
 	 * crash recovery).
 	 */
-	if (!ArchiveRecoveryRequested)
+	if (!ArchiveRecoveryRequested())
 		goto not_available;
 
 	/* In standby mode, restore_command might not be supplied */
@@ -205,7 +205,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
 				 * incorrectly conclude we've reached the end of WAL and we're
 				 * done recovering ...
 				 */
-				if (StandbyMode && stat_buf.st_size < expectedSize)
+				if (InStandbyMode() && stat_buf.st_size < expectedSize)
 					elevel = DEBUG1;
 				else
 					elevel = FATAL;
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 0bbe2eea206..e75c7891fd5 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -123,30 +123,7 @@ TimeLineID	recoveryTargetTLI = 0;
 static List *expectedTLEs;
 static TimeLineID curFileTLI;
 
-/*
- * When ArchiveRecoveryRequested is set, archive recovery was requested,
- * ie. signal files were present.  When InArchiveRecovery is set, we are
- * currently recovering using offline XLOG archives.  These variables are only
- * valid in the startup process.
- *
- * When ArchiveRecoveryRequested is true, but InArchiveRecovery is false, we're
- * currently performing crash recovery using only XLOG files in pg_wal, but
- * will switch to using offline XLOG archives as soon as we reach the end of
- * WAL in pg_wal.
- */
-bool		ArchiveRecoveryRequested = false;
-bool		InArchiveRecovery = false;
-
-/*
- * When StandbyModeRequested is set, standby mode was requested, i.e.
- * standby.signal file was present.  When StandbyMode is set, we are currently
- * in standby mode.  These variables are only valid in the startup process.
- * They work similarly to ArchiveRecoveryRequested and InArchiveRecovery.
- */
-static bool StandbyModeRequested = false;
-bool		StandbyMode = false;
-
-/* was a signal file present at startup? */
+/* Was a signal file present at startup? */
 static bool standby_signal_file_found = false;
 static bool recovery_signal_file_found = false;
 
@@ -170,16 +147,19 @@ static XLogRecPtr RedoStartLSN = InvalidXLogRecPtr;
 static TimeLineID RedoStartTLI = 0;
 
 /*
- * Local copy of SharedHotStandbyActive variable. False actually means "not
- * known, need to check the shared state".
- */
-static bool LocalHotStandbyActive = false;
-
-/*
- * Local copy of SharedPromoteIsTriggered variable. False actually means "not
- * known, need to check the shared state".
+ * Local flags:
+ * SX_ARCHIVE_RECOVERY_REQUESTED
+ * SX_IN_ARCHIVE_RECOVERY
+ * SX_STANDBY_MODE_REQUESTED
+ * SX_IN_STANDBY_MODE
+ *
+ * and local copies of sharedRecoveryFlags:
+ * SX_HOT_STANDBY_ACTIVE,
+ * SX_PROMOTE_IS_TRIGGERED.
+ * If some flag is not set, that actually means "not known, need to check
+ * the shared state".
  */
-static bool LocalPromoteIsTriggered = false;
+static bits32 localRecoveryFlags = 0;
 
@@ -297,23 +277,16 @@ bool		reachedConsistency = false;
 
-
 /*
  * Shared-memory state for WAL recovery.
  */
 typedef struct XLogRecoveryCtlData
 {
 	/*
-	 * SharedHotStandbyActive indicates if we allow hot standby queries to be
-	 * run.  Protected by info_lck.
+	 * The bit array stores the following states
+	 * SX_HOT_STANDBY_ACTIVE, SX_PROMOTE_IS_TRIGGERED. Protected by info_lck.
 	 */
-	bool		SharedHotStandbyActive;
-
-	/*
-	 * SharedPromoteIsTriggered indicates if a standby promotion has been
-	 * triggered.  Protected by info_lck.
-	 */
-	bool		SharedPromoteIsTriggered;
+	bits32		sharedRecoveryFlags;
 
 	/*
 	 * recoveryWakeupLatch is used to wake up the startup process to continue
@@ -440,6 +413,7 @@ static bool HotStandbyActiveInReplay(void);
 static void SetCurrentChunkStartTime(TimestampTz xtime);
 static void SetLatestXTime(TimestampTz xtime);
 
+static bool StandbyModeRequested(void);
 /*
  * Initialization of shared memory for WAL recovery
  */
@@ -477,7 +451,7 @@ XLogRecoveryShmemInit(void)
 static void
 EnableStandbyMode(void)
 {
-	StandbyMode = true;
+	localRecoveryFlags |= SX_IN_STANDBY_MODE;
 
 	/*
 	 * To avoid server log bloat, we don't report recovery progress in a
@@ -505,8 +479,8 @@ EnableStandbyMode(void)
  * disk does after initializing other subsystems, but before calling
  * PerformWalRecovery().
  *
- * This initializes some global variables like ArchiveRecoveryRequested, and
- * StandbyModeRequested and InRecovery.
+ * This initializes some flags like SX_ARCHIVE_RECOVERY_REQUESTED and
+ * SX_STABDBY_MODE_REQUESTED and global variable InRecovery.
  */
 void
 InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
@@ -544,7 +518,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 	 * Take ownership of the wakeup latch if we're going to sleep during
 	 * recovery, if required.
 	 */
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 		OwnLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 
 	/*
@@ -599,8 +573,8 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 		 * file, we know how far we need to replay to reach consistency. Enter
 		 * archive recovery directly.
 		 */
-		InArchiveRecovery = true;
-		if (StandbyModeRequested)
+		localRecoveryFlags |= SX_IN_ARCHIVE_RECOVERY;
+		if (StandbyModeRequested())
 			EnableStandbyMode();
 
 		/*
@@ -749,14 +723,14 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 		 * to minRecoveryPoint, up to backupEndPoint, or until we see an
 		 * end-of-backup record), and we can enter archive recovery directly.
 		 */
-		if (ArchiveRecoveryRequested &&
+		if (ArchiveRecoveryRequested() &&
 			(ControlFile->minRecoveryPoint != InvalidXLogRecPtr ||
 			 ControlFile->backupEndRequired ||
 			 ControlFile->backupEndPoint != InvalidXLogRecPtr ||
 			 ControlFile->state == DB_SHUTDOWNED))
 		{
-			InArchiveRecovery = true;
-			if (StandbyModeRequested)
+			localRecoveryFlags |= SX_IN_ARCHIVE_RECOVERY;
+			if (StandbyModeRequested())
 				EnableStandbyMode();
 		}
 
@@ -799,9 +773,9 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 		wasShutdown = ((record->xl_info & ~XLR_INFO_MASK) == XLOG_CHECKPOINT_SHUTDOWN);
 	}
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
-		if (StandbyModeRequested)
+		if (StandbyModeRequested())
 			ereport(LOG,
 					(errmsg("entering standby mode")));
 		else if (recoveryTarget == RECOVERY_TARGET_XID)
@@ -911,7 +885,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 	}
 	else if (ControlFile->state != DB_SHUTDOWNED)
 		InRecovery = true;
-	else if (ArchiveRecoveryRequested)
+	else if (ArchiveRecoveryRequested())
 	{
 		/* force recovery due to presence of recovery signal file */
 		InRecovery = true;
@@ -928,7 +902,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 	 */
 	if (InRecovery)
 	{
-		if (InArchiveRecovery)
+		if (InArchiveRecovery())
 		{
 			ControlFile->state = DB_IN_ARCHIVE_RECOVERY;
 		}
@@ -947,7 +921,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 		}
 		ControlFile->checkPoint = CheckPointLoc;
 		ControlFile->checkPointCopy = checkPoint;
-		if (InArchiveRecovery)
+		if (InArchiveRecovery())
 		{
 			/* initialize minRecoveryPoint if not set yet */
 			if (ControlFile->minRecoveryPoint < checkPoint.redo)
@@ -994,7 +968,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 	backupStartPoint = ControlFile->backupStartPoint;
 	backupEndRequired = ControlFile->backupEndRequired;
 	backupEndPoint = ControlFile->backupEndPoint;
-	if (InArchiveRecovery)
+	if (InArchiveRecovery())
 	{
 		minRecoveryPoint = ControlFile->minRecoveryPoint;
 		minRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
@@ -1080,17 +1054,16 @@ readRecoverySignalFile(void)
 		recovery_signal_file_found = true;
 	}
 
-	StandbyModeRequested = false;
-	ArchiveRecoveryRequested = false;
+	localRecoveryFlags &= ~SX_STANDBY_MODE_REQUESTED;
+	localRecoveryFlags &= ~SX_ARCHIVE_RECOVERY_REQUESTED;
 	if (standby_signal_file_found)
 	{
-		StandbyModeRequested = true;
-		ArchiveRecoveryRequested = true;
+		localRecoveryFlags |= SX_STANDBY_MODE_REQUESTED;
+		localRecoveryFlags |= SX_ARCHIVE_RECOVERY_REQUESTED;
 	}
 	else if (recovery_signal_file_found)
 	{
-		StandbyModeRequested = false;
-		ArchiveRecoveryRequested = true;
+		localRecoveryFlags |= SX_ARCHIVE_RECOVERY_REQUESTED;
 	}
 	else
 		return;
@@ -1099,7 +1072,7 @@ readRecoverySignalFile(void)
 	 * We don't support standby mode in standalone backends; that requires
 	 * other processes such as the WAL receiver to be alive.
 	 */
-	if (StandbyModeRequested && !IsUnderPostmaster)
+	if (StandbyModeRequested() && !IsUnderPostmaster)
 		ereport(FATAL,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("standby mode is not supported by single-user servers")));
@@ -1108,13 +1081,13 @@ readRecoverySignalFile(void)
 static void
 validateRecoveryParameters(void)
 {
-	if (!ArchiveRecoveryRequested)
+	if (!ArchiveRecoveryRequested())
 		return;
 
 	/*
 	 * Check for compulsory parameters
 	 */
-	if (StandbyModeRequested)
+	if (StandbyModeRequested())
 	{
 		if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
 			(recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
@@ -1155,8 +1128,8 @@ validateRecoveryParameters(void)
 	/*
 	 * If user specified recovery_target_timeline, validate it or compute the
 	 * "latest" value.  We can't do this until after we've gotten the restore
-	 * command and set InArchiveRecovery, because we need to fetch timeline
-	 * history files from the archive.
+	 * command and set SX_IN_ARCHIVE_RECOVERY, because we need to fetch
+	 * timeline history files from the archive.
 	 */
 	if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC)
 	{
@@ -1494,7 +1467,7 @@ FinishWalRecovery(void)
 	 * i.e., calling XLogShutdownWalRcv().
 	 */
 	Assert(!WalRcvStreaming());
-	StandbyMode = false;
+	localRecoveryFlags &= ~SX_IN_STANDBY_MODE;
 
 	/*
 	 * Determine where to start writing WAL next.
@@ -1532,7 +1505,7 @@ FinishWalRecovery(void)
 	 */
 	result->endOfLogTLI = xlogreader->seg.ws_tli;
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		/*
 		 * We are no longer in archive recovery state.
@@ -1540,8 +1513,8 @@ FinishWalRecovery(void)
 		 * We are now done reading the old WAL.  Turn off archive fetching if
 		 * it was active.
 		 */
-		Assert(InArchiveRecovery);
-		InArchiveRecovery = false;
+		Assert(InArchiveRecovery());
+		localRecoveryFlags &= ~SX_IN_ARCHIVE_RECOVERY;
 
 		/*
 		 * If the ending log segment is still open, close it (to avoid
@@ -1621,7 +1594,7 @@ ShutdownWalRecovery(void)
 	XLogReaderFree(xlogreader);
 	XLogPrefetcherFree(xlogprefetcher);
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		/*
 		 * Since there might be a partial WAL segment named RECOVERYXLOG, get
@@ -1639,7 +1612,7 @@ ShutdownWalRecovery(void)
 	 * We don't need the latch anymore. It's not strictly necessary to disown
 	 * it, but let's do it for the sake of tidiness.
 	 */
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 		DisownLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 }
 
@@ -1741,7 +1714,7 @@ PerformWalRecovery(void)
 						LSN_FORMAT_ARGS(xlogreader->ReadRecPtr))));
 
 		/* Prepare to report progress of the redo phase. */
-		if (!StandbyMode)
+		if (!InStandbyMode())
 			begin_startup_progress_phase();
 
 		/*
@@ -1749,7 +1722,7 @@ PerformWalRecovery(void)
 		 */
 		do
 		{
-			if (!StandbyMode)
+			if (!InStandbyMode())
 				ereport_startup_progress("redo in progress, elapsed time: %ld.%02d s, current LSN: %X/%X",
 										 LSN_FORMAT_ARGS(xlogreader->ReadRecPtr));
 
@@ -1894,7 +1867,7 @@ PerformWalRecovery(void)
 	 * This check is intentionally after the above log messages that indicate
 	 * how far recovery went.
 	 */
-	if (ArchiveRecoveryRequested &&
+	if (ArchiveRecoveryRequested() &&
 		recoveryTarget != RECOVERY_TARGET_UNSET &&
 		!reachedRecoveryTarget)
 		ereport(FATAL,
@@ -2186,7 +2159,7 @@ CheckRecoveryConsistency(void)
 	if (XLogRecPtrIsInvalid(minRecoveryPoint))
 		return;
 
-	Assert(InArchiveRecovery);
+	Assert(InArchiveRecovery());
 
 	/*
 	 * assume that we are called in the startup process, and hence don't need
@@ -2256,15 +2229,15 @@ CheckRecoveryConsistency(void)
 	 * enabling connections.
 	 */
 	if (standbyState == STANDBY_SNAPSHOT_READY &&
-		!LocalHotStandbyActive &&
+		!(localRecoveryFlags & SX_HOT_STANDBY_ACTIVE) &&
 		reachedConsistency &&
 		IsUnderPostmaster)
 	{
 		SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-		XLogRecoveryCtl->SharedHotStandbyActive = true;
+		XLogRecoveryCtl->sharedRecoveryFlags |= SX_HOT_STANDBY_ACTIVE;
 		SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
-		LocalHotStandbyActive = true;
+		localRecoveryFlags |= SX_HOT_STANDBY_ACTIVE;
 
 		SendPostmasterSignal(PMSIGNAL_BEGIN_HOT_STANDBY);
 	}
@@ -2584,7 +2557,7 @@ recoveryStopsBefore(XLogReaderState *record)
 	 * Ignore recovery target settings when not in archive recovery (meaning
 	 * we are in crash recovery).
 	 */
-	if (!ArchiveRecoveryRequested)
+	if (!ArchiveRecoveryRequested())
 		return false;
 
 	/* Check if we should stop as soon as reaching consistency */
@@ -2736,7 +2709,7 @@ recoveryStopsAfter(XLogReaderState *record)
 	 * Ignore recovery target settings when not in archive recovery (meaning
 	 * we are in crash recovery).
 	 */
-	if (!ArchiveRecoveryRequested)
+	if (!ArchiveRecoveryRequested())
 		return false;
 
 	info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
@@ -2927,11 +2900,11 @@ static void
 recoveryPausesHere(bool endOfRecovery)
 {
 	/* Don't pause unless users can connect! */
-	if (!LocalHotStandbyActive)
+	if (!(localRecoveryFlags & SX_HOT_STANDBY_ACTIVE))
 		return;
 
 	/* Don't pause after standby promotion has been triggered */
-	if (LocalPromoteIsTriggered)
+	if (localRecoveryFlags & SX_PROMOTE_IS_TRIGGERED)
 		return;
 
 	if (endOfRecovery)
@@ -2997,7 +2970,7 @@ recoveryApplyDelay(XLogReaderState *record)
 		return false;
 
 	/* nothing to do if crash recovery is requested */
-	if (!ArchiveRecoveryRequested)
+	if (!ArchiveRecoveryRequested())
 		return false;
 
 	/*
@@ -3159,13 +3132,13 @@ ReadRecord(XLogPrefetcher *xlogprefetcher, int emode,
 			 * to indicate to downstream WAL readers that that portion is to
 			 * be ignored.
 			 *
-			 * However, when ArchiveRecoveryRequested = true, we're going to
+			 * However, when ArchiveRecoveryRequested() = true, we're going to
 			 * switch to a new timeline at the end of recovery. We will only
 			 * copy WAL over to the new timeline up to the end of the last
 			 * complete record, so if we did this, we would later create an
 			 * overwrite contrecord in the wrong place, breaking everything.
 			 */
-			if (!ArchiveRecoveryRequested &&
+			if (!ArchiveRecoveryRequested() &&
 				!XLogRecPtrIsInvalid(xlogreader->abortedRecPtr))
 			{
 				abortedRecPtr = xlogreader->abortedRecPtr;
@@ -3234,13 +3207,13 @@ ReadRecord(XLogPrefetcher *xlogprefetcher, int emode,
 			 * we'd have no idea how far we'd have to replay to reach
 			 * consistency.  So err on the safe side and give up.
 			 */
-			if (!InArchiveRecovery && ArchiveRecoveryRequested &&
+			if (!InArchiveRecovery() && ArchiveRecoveryRequested() &&
 				!fetching_ckpt)
 			{
 				ereport(DEBUG1,
 						(errmsg_internal("reached end of WAL in pg_wal, entering archive recovery")));
-				InArchiveRecovery = true;
-				if (StandbyModeRequested)
+				localRecoveryFlags |= SX_IN_ARCHIVE_RECOVERY;
+				if (StandbyModeRequested())
 					EnableStandbyMode();
 
 				SwitchIntoArchiveRecovery(xlogreader->EndRecPtr, replayTLI);
@@ -3260,7 +3233,7 @@ ReadRecord(XLogPrefetcher *xlogprefetcher, int emode,
 			}
 
 			/* In standby mode, loop back to retry. Otherwise, give up. */
-			if (StandbyMode && !CheckForStandbyTrigger())
+			if (InStandbyMode() && !CheckForStandbyTrigger())
 				continue;
 			else
 				return NULL;
@@ -3321,7 +3294,7 @@ XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
 		 * Request a restartpoint if we've replayed too much xlog since the
 		 * last one.
 		 */
-		if (ArchiveRecoveryRequested && IsUnderPostmaster)
+		if (ArchiveRecoveryRequested() && IsUnderPostmaster)
 		{
 			if (XLogCheckpointNeeded(readSegNo))
 			{
@@ -3464,7 +3437,7 @@ retry:
 	 * page header here for the retry. Instead, ReadPageInternal() is
 	 * responsible for the validation.
 	 */
-	if (StandbyMode &&
+	if (InStandbyMode() &&
 		!XLogReaderValidatePageHeader(xlogreader, targetPagePtr, readBuf))
 	{
 		/*
@@ -3500,7 +3473,7 @@ next_record_is_invalid:
 	readSource = XLOG_FROM_ANY;
 
 	/* In standby-mode, keep trying */
-	if (StandbyMode)
+	if (InStandbyMode())
 		goto retry;
 	else
 		return XLREAD_FAIL;
@@ -3575,10 +3548,10 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 	 * the end of recovery.
 	 *-------
 	 */
-	if (!InArchiveRecovery)
+	if (!InArchiveRecovery())
 		currentSource = XLOG_FROM_PG_WAL;
 	else if (currentSource == XLOG_FROM_ANY ||
-			 (!StandbyMode && currentSource == XLOG_FROM_STREAM))
+			 (!InStandbyMode() && currentSource == XLOG_FROM_STREAM))
 	{
 		lastSourceFailed = false;
 		currentSource = XLOG_FROM_ARCHIVE;
@@ -3616,7 +3589,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 					 * finish replaying as much as we can from archive and
 					 * pg_wal before failover.
 					 */
-					if (StandbyMode && CheckForStandbyTrigger())
+					if (InStandbyMode() && CheckForStandbyTrigger())
 					{
 						XLogShutdownWalRcv();
 						return XLREAD_FAIL;
@@ -3626,7 +3599,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 					 * Not in standby mode, and we've now tried the archive
 					 * and pg_wal.
 					 */
-					if (!StandbyMode)
+					if (!InStandbyMode())
 						return XLREAD_FAIL;
 
 					/*
@@ -3658,7 +3631,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 					 * We should be able to move to XLOG_FROM_STREAM only in
 					 * standby mode.
 					 */
-					Assert(StandbyMode);
+					Assert(InStandbyMode());
 
 					/*
 					 * Before we leave XLOG_FROM_STREAM state, make sure that
@@ -3729,7 +3702,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 			 * the archive over ones in pg_wal, so try the next file again
 			 * from the archive first.
 			 */
-			if (InArchiveRecovery)
+			if (InArchiveRecovery())
 				currentSource = XLOG_FROM_ARCHIVE;
 		}
 
@@ -3789,7 +3762,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 					 * We should be able to move to XLOG_FROM_STREAM only in
 					 * standby mode.
 					 */
-					Assert(StandbyMode);
+					Assert(InStandbyMode());
 
 					/*
 					 * First, shutdown walreceiver if its restart has been
@@ -4397,21 +4370,22 @@ PromoteIsTriggered(void)
 	 * triggered. We can't trigger a promotion again, so there's no need to
 	 * keep checking after the shared variable has once been seen true.
 	 */
-	if (LocalPromoteIsTriggered)
+	if (localRecoveryFlags & SX_PROMOTE_IS_TRIGGERED)
 		return true;
 
 	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-	LocalPromoteIsTriggered = XLogRecoveryCtl->SharedPromoteIsTriggered;
+	localRecoveryFlags |=
+		(XLogRecoveryCtl->sharedRecoveryFlags & SX_PROMOTE_IS_TRIGGERED);
 	SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
-	return LocalPromoteIsTriggered;
+	return localRecoveryFlags & SX_PROMOTE_IS_TRIGGERED;
 }
 
 static void
+	XLogRecoveryCtl->sharedRecoveryFlags |= SX_PROMOTE_IS_TRIGGERED;
 
 	/*
@@ -4422,7 +4396,7 @@ SetPromoteIsTriggered(void)
 	 */
 	SetRecoveryPause(false);
 
-	LocalPromoteIsTriggered = true;
+	localRecoveryFlags |= SX_PROMOTE_IS_TRIGGERED;
 }
 
 /*
@@ -4431,7 +4405,7 @@ SetPromoteIsTriggered(void)
 static bool
 CheckForStandbyTrigger(void)
 {
-	if (LocalPromoteIsTriggered)
+	if (localRecoveryFlags & SX_PROMOTE_IS_TRIGGERED)
 		return true;
 
 	if (IsPromoteSignaled() && CheckPromoteSignal())
@@ -4505,16 +4479,17 @@ HotStandbyActive(void)
 	 * can't de-activate Hot Standby, so there's no need to keep checking
 	 * after the shared variable has once been seen true.
 	 */
-	if (LocalHotStandbyActive)
+	if (localRecoveryFlags & SX_HOT_STANDBY_ACTIVE)
 		return true;
 	else
 	{
 		/* spinlock is essential on machines with weak memory ordering! */
 		SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-		LocalHotStandbyActive = XLogRecoveryCtl->SharedHotStandbyActive;
+		localRecoveryFlags |=
+			(XLogRecoveryCtl->sharedRecoveryFlags & SX_HOT_STANDBY_ACTIVE);
 		SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
-		return LocalHotStandbyActive;
+		return localRecoveryFlags & SX_HOT_STANDBY_ACTIVE;
 	}
 }
 
@@ -4526,7 +4501,7 @@ static bool
 HotStandbyActiveInReplay(void)
 {
 	Assert(AmStartupProcess() || !IsPostmasterEnvironment);
-	return LocalHotStandbyActive;
+	return localRecoveryFlags & SX_HOT_STANDBY_ACTIVE;
 }
 
 /*
@@ -5044,3 +5019,23 @@ assign_recovery_target_xid(const char *newval, void *extra)
 	else
 		recoveryTarget = RECOVERY_TARGET_UNSET;
 }
+
+bool StandbyModeRequested(void)
+{
+	return localRecoveryFlags & SX_STANDBY_MODE_REQUESTED;
+}
+
+bool ArchiveRecoveryRequested(void)
+{
+	return localRecoveryFlags & SX_ARCHIVE_RECOVERY_REQUESTED;
+}
+
+bool InArchiveRecovery(void)
+{
+	return localRecoveryFlags & SX_IN_ARCHIVE_RECOVERY;
+}
+
+bool InStandbyMode(void)
+{
+	return localRecoveryFlags & SX_IN_STANDBY_MODE;
+}
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index f6945af1d43..b8ac4c6c5df 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1517,7 +1517,7 @@ update_synced_slots_inactive_since(void)
 	 * long time after promotion if they haven't been synchronized recently.
 	 * Whoever acquires the slot, i.e., makes the slot active, will reset it.
 	 */
-	if (!StandbyMode)
+	if (!InStandbyMode())
 		return;
 
 	/* The slot sync worker or SQL function mustn't be running by now */
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 0b7c56332b5..ff5e4fce305 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -392,14 +392,13 @@ extern void GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli);
 extern void XLogRecGetBlockRefInfo(XLogReaderState *record, bool pretty,
 								   bool detailed_format, StringInfo buf,
 								   uint32 *fpi_len);
-
 /*
  * Exported for the functions in timeline.c and xlogarchive.c.  Only valid
  * in the startup process.
  */
-extern PGDLLIMPORT bool ArchiveRecoveryRequested;
-extern PGDLLIMPORT bool InArchiveRecovery;
-extern PGDLLIMPORT bool StandbyMode;
+extern PGDLLIMPORT bool ArchiveRecoveryRequested(void);
+extern PGDLLIMPORT bool InArchiveRecovery(void);
+extern PGDLLIMPORT bool InStandbyMode(void);
 extern PGDLLIMPORT char *recoveryRestoreCommand;
 
 #endif							/* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 91446303024..2d44905ea36 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -16,6 +16,37 @@
 #include "lib/stringinfo.h"
 #include "utils/timestamp.h"
 
+/*
+ * The flag indicates if we allow hot standby queries to be run.
+ */
+#define SX_HOT_STANDBY_ACTIVE			0x01	/* SX: Startup Xlog */
+/*
+ * The flag indicates if a standby promotion has been triggered.
+ */
+#define SX_PROMOTE_IS_TRIGGERED			0x02
+/*
+ * When SX_ARCHIVE_RECOVERY_REQUESTED is set, archive recovery was requested,
+ * i.e. signal files were present.  When SX_IN_ARCHIVE_RECOVERY is set, we are
+ * currently recovering using offline XLOG archives.  These variables are only
+ * valid in the startup process.
+ *
+ * When SX_ARCHIVE_RECOVERY_REQUESTED is set, but SX_IN_ARCHIVE_RECOVERY is
+ * not, we're currently performing crash recovery using only XLOG files in
+ * pg_wal, but will switch to using offline XLOG archives as soon as we reach
+ * the end of WAL in pg_wal.
+ */
+#define SX_ARCHIVE_RECOVERY_REQUESTED	0x04
+#define SX_IN_ARCHIVE_RECOVERY			0x08
+/*
+ * When SX_STANDBY_MODE_REQUESTED is set, standby mode was requested, i.e.
+ * standby.signal file was present.  When SX_IN_STANDBY_MODE is set, we are
+ * currently in standby mode.  These variables are only valid in the startup
+ * process. They work similarly to SX_ARCHIVE_RECOVERY_REQUESTED and
+ * SX_IN_ARCHIVE_RECOVERY.
+ */
+#define SX_STANDBY_MODE_REQUESTED		0x10
+#define SX_IN_STANDBY_MODE				0x20
+
 /*
  * Recovery target type.
  * Only set during a Point in Time recovery, not when in standby mode.
@@ -73,9 +104,6 @@ extern PGDLLIMPORT TimeLineID recoveryTargetTLI;
 /* Have we already reached a consistent database state? */
 extern PGDLLIMPORT bool reachedConsistency;
 
-/* Are we currently in standby mode? */
-extern PGDLLIMPORT bool StandbyMode;
-
 extern Size XLogRecoveryShmemSize(void);
 extern void XLogRecoveryShmemInit(void);
 
-- 
2.34.1

From 39c42bdcaa371ea356ff2db8944d4cf8535d23ba Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <[email-masked]>
Date: Fri, 10 Jan 2025 22:31:34 +0300
Subject: [PATCH] Wrapper function to extract whole text array from recovery
 flags bitset.

It returns SX_PROMOTE_IS_TRIGGERED, SX_STANDBY_MODE_REQUESTED  flags for
recovery states.
---
 src/backend/access/transam/xlogfuncs.c    | 31 +++++++++++++++++++++++
 src/backend/access/transam/xlogrecovery.c | 16 ++++++++++++
 src/include/access/xlogrecovery.h         |  1 +
 src/include/catalog/pg_proc.dat           |  5 ++++
 4 files changed, 53 insertions(+)

diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 8c3090165f0..9b71ff7583c 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -30,6 +30,7 @@
 #include "storage/fd.h"
 #include "storage/latch.h"
 #include "storage/standby.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
@@ -748,3 +749,33 @@ pg_promote(PG_FUNCTION_ARGS)
 						   wait_seconds)));
 	PG_RETURN_BOOL(false);
 }
+
+Datum
+pg_get_recovery_flags(PG_FUNCTION_ARGS)
+{
+/*
+ * Currently supported number of recovery flags is equal to two:
+ * {SX_PROMOTE_IS_TRIGGERED, SX_STANDBY_MODE_REQUESTED}.
+ * The SX_STANDBY_MODE_REQUESTED is valid only in the startup process.
+ */
+#define MAX_RECOVERY_FLAGS 2
+
+	bits32			recovery_flags;
+	int				cnt = 0;
+	Datum			flags[MAX_RECOVERY_FLAGS];
+	ArrayType	   *txt_arr;
+
+	recovery_flags = GetXLogRecoveryFlags();
+
+	if (recovery_flags & SX_PROMOTE_IS_TRIGGERED)
+		flags[cnt++] = CStringGetTextDatum("PROMOTE_IS_TRIGGERED");
+
+	if (recovery_flags & SX_STANDBY_MODE_REQUESTED)
+		flags[cnt++] = CStringGetTextDatum("STANDBY_MODE_REQUESTED");
+
+	Assert(cnt <= MAX_RECOVERY_FLAGS);
+
+	/* Returns bit array as Datum */
+	txt_arr = construct_array_builtin(flags, cnt, TEXTOID);
+	PG_RETURN_ARRAYTYPE_P(txt_arr);
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index e75c7891fd5..d675b46c592 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4355,6 +4355,22 @@ StartupRequestWalReceiverRestart(void)
 	}
 }
 
+/*
+ * Return SX_PROMOTE_IS_TRIGGERED, SX_STANDBY_MODE_REQUESTED flags for
+ * recovery states.
+ */
+bits32 GetXLogRecoveryFlags(void)
+{
+	bits32		flags = 0;
+
+	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
+	flags = XLogRecoveryCtl->sharedRecoveryFlags;
+	SpinLockRelease(&XLogRecoveryCtl->info_lck);
+
+	flags |= (localRecoveryFlags & SX_STANDBY_MODE_REQUESTED);
+
+	return flags;
+}
 
 /*
  * Has a standby promotion already been triggered?
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 2d44905ea36..faa6c666ede 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -172,6 +172,7 @@ extern TimestampTz GetLatestXTime(void);
 extern TimestampTz GetCurrentChunkReplayStartTime(void);
 extern XLogRecPtr GetCurrentReplayRecPtr(TimeLineID *replayEndTLI);
 
index b37e8a6f882..e1e10f6a5d6 100644
@@ -6660,6 +6660,11 @@
 
+{ oid => '8439',
   prorettype => 'pg_lsn', proargtypes => '',
-- 
2.34.1

---

From: [email-masked]
Date: Fri, 28 Feb 2025 16:41:58 +0300

Hi!

Rebased the patch.

Respectfully,

Mikhail Litsarev,

From 67b190ce15f8ba7480ba1691b804b27f96fd5540 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <[email-masked]>
Date: Fri, 10 Jan 2025 21:23:02 +0300
Subject: [PATCH v5 1/2] Replace recovery boolean flags with a bits32 set.

Refactor the code according to the changes.
---

 
index 799fc739e18..f3af732b229 100644
@@ -5434,7 +5434,7 @@ CheckRequiredParameterValues(void)
@@ -5447,7 +5447,7 @@ CheckRequiredParameterValues(void)
@@ -5607,8 +5607,8 @@ StartupXLOG(void)
@@ -5740,7 +5740,7 @@ StartupXLOG(void)
@@ -5793,7 +5793,7 @@ StartupXLOG(void)
@@ -5827,7 +5827,7 @@ StartupXLOG(void)
@@ -5940,7 +5940,7 @@ StartupXLOG(void)
@@ -5992,7 +5992,7 @@ StartupXLOG(void)
@@ -6185,7 +6185,7 @@ StartupXLOG(void)
 	XLogReportParameters();

 	/*
@@ -6344,7 +6344,7 @@ PerformRecoveryXLogAction(void)
@@ -8338,7 +8338,7 @@ xlog_redo(XLogReaderState *record)
@@ -8579,7 +8579,7 @@ xlog_redo(XLogReaderState *record)
 
index 52f53fa12e0..3d1fe965a45 100644
@@ -124,30 +124,7 @@ TimeLineID	recoveryTargetTLI = 0;
 
-/*
 
@@ -171,16 +148,19 @@ static XLogRecPtr RedoStartLSN = InvalidXLogRecPtr;
 static TimeLineID RedoStartTLI = 0;
 
 /*
 
@@ -298,23 +278,16 @@ bool		reachedConsistency = false;
 
-
 /*
 
 	/*
 	 * recoveryWakeupLatch is used to wake up the startup process to continue
@@ -441,6 +414,7 @@ static bool HotStandbyActiveInReplay(void);
 static void SetCurrentChunkStartTime(TimestampTz xtime);
 static void SetLatestXTime(TimestampTz xtime);
 
+static bool StandbyModeRequested(void);
 /*
  * Initialization of shared memory for WAL recovery
  */
@@ -478,7 +452,7 @@ XLogRecoveryShmemInit(void)
 static void
 EnableStandbyMode(void)
 {
-	StandbyMode = true;
+	localRecoveryFlags |= SX_IN_STANDBY_MODE;
 
 	/*
@@ -506,8 +480,8 @@ EnableStandbyMode(void)
@@ -545,7 +519,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 
 	/*
@@ -600,8 +574,8 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 
 		/*
@@ -750,14 +724,14 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 
@@ -800,9 +774,9 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 		wasShutdown = ((record->xl_info & ~XLR_INFO_MASK) == XLOG_CHECKPOINT_SHUTDOWN);
 	}
 
@@ -914,7 +888,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
@@ -931,7 +905,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
@@ -950,7 +924,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
@@ -997,7 +971,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
@@ -1083,17 +1057,16 @@ readRecoverySignalFile(void)
 
@@ -1102,7 +1075,7 @@ readRecoverySignalFile(void)
@@ -1111,13 +1084,13 @@ readRecoverySignalFile(void)
 
 	/*
@@ -1158,8 +1131,8 @@ validateRecoveryParameters(void)
@@ -1497,7 +1470,7 @@ FinishWalRecovery(void)
 
 	/*
 	 * Determine where to start writing WAL next.
@@ -1535,7 +1508,7 @@ FinishWalRecovery(void)
 	 */
 	result->endOfLogTLI = xlogreader->seg.ws_tli;
 
@@ -1543,8 +1516,8 @@ FinishWalRecovery(void)
 
 		/*
 		 * If the ending log segment is still open, close it (to avoid
@@ -1624,7 +1597,7 @@ ShutdownWalRecovery(void)
 	XLogReaderFree(xlogreader);
 	XLogPrefetcherFree(xlogprefetcher);
 
@@ -1642,7 +1615,7 @@ ShutdownWalRecovery(void)
 
@@ -1744,7 +1717,7 @@ PerformWalRecovery(void)
 						LSN_FORMAT_ARGS(xlogreader->ReadRecPtr))));

 		/*
@@ -1752,7 +1725,7 @@ PerformWalRecovery(void)
 		 */
 		do
 		{
-			if (!StandbyMode)
+			if (!InStandbyMode())
 				ereport_startup_progress("redo in progress, elapsed time: %ld.%02d s, current LSN: %X/%X",
 										 LSN_FORMAT_ARGS(xlogreader->ReadRecPtr));
 
@@ -1897,7 +1870,7 @@ PerformWalRecovery(void)
 	 * This check is intentionally after the above log messages that indicate
 	 * how far recovery went.
 	 */
-	if (ArchiveRecoveryRequested &&
+	if (ArchiveRecoveryRequested() &&
 		recoveryTarget != RECOVERY_TARGET_UNSET &&
 		!reachedRecoveryTarget)
 		ereport(FATAL,
@@ -2189,7 +2162,7 @@ CheckRecoveryConsistency(void)
 	if (XLogRecPtrIsInvalid(minRecoveryPoint))
 		return;

 	/*
@@ -2259,15 +2232,15 @@ CheckRecoveryConsistency(void)

@@ -2587,7 +2560,7 @@ recoveryStopsBefore(XLogReaderState *record)
 
@@ -2739,7 +2712,7 @@ recoveryStopsAfter(XLogReaderState *record)
 
@@ -2930,11 +2903,11 @@ static void

 	if (endOfRecovery)
@@ -3000,7 +2973,7 @@ recoveryApplyDelay(XLogReaderState *record)
 		return false;

 	/*
@@ -3162,13 +3135,13 @@ ReadRecord(XLogPrefetcher *xlogprefetcher, int emode,
@@ -3237,13 +3210,13 @@ ReadRecord(XLogPrefetcher *xlogprefetcher, int emode,
 
 				SwitchIntoArchiveRecovery(xlogreader->EndRecPtr, replayTLI);
@@ -3263,7 +3236,7 @@ ReadRecord(XLogPrefetcher *xlogprefetcher, int emode,
 			}
 
 			/* In standby mode, loop back to retry. Otherwise, give up. */
-			if (StandbyMode && !CheckForStandbyTrigger())
+			if (InStandbyMode() && !CheckForStandbyTrigger())
 				continue;
 			else
 				return NULL;
@@ -3325,7 +3298,7 @@ XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen,
 		 * Request a restartpoint if we've replayed too much xlog since the
 		 * last one.
 		 */
-		if (ArchiveRecoveryRequested && IsUnderPostmaster)
+		if (ArchiveRecoveryRequested() && IsUnderPostmaster)
 		{
 			if (XLogCheckpointNeeded(readSegNo))
 			{
@@ -3478,7 +3451,7 @@ retry:
 	 * page header here for the retry. Instead, ReadPageInternal() is
 	 * responsible for the validation.
 	 */
-	if (StandbyMode &&
+	if (InStandbyMode() &&
 		(targetPagePtr % wal_segment_size) == 0 &&
 		!XLogReaderValidatePageHeader(xlogreader, targetPagePtr, readBuf))
 	{
@@ -3515,7 +3488,7 @@ next_record_is_invalid:
 	readSource = XLOG_FROM_ANY;
 
@@ -3590,10 +3563,10 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
@@ -3631,7 +3604,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
@@ -3641,7 +3614,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 
 					/*
@@ -3673,7 +3646,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 					 * We should be able to move to XLOG_FROM_STREAM only in
 					 * standby mode.
 					 */
-					Assert(StandbyMode);
+					Assert(InStandbyMode());
 
 					/*
@@ -3744,7 +3717,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 
@@ -3804,7 +3777,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 					 * We should be able to move to XLOG_FROM_STREAM only in
 					 * standby mode.
 					 */
-					Assert(StandbyMode);
+					Assert(InStandbyMode());
 
 					/*
@@ -4412,21 +4385,22 @@ PromoteIsTriggered(void)

 
 static void
 
 	/*
@@ -4437,7 +4411,7 @@ SetPromoteIsTriggered(void)
 	 */
 	SetRecoveryPause(false);

 /*
@@ -4446,7 +4420,7 @@ SetPromoteIsTriggered(void)
 static bool
 CheckForStandbyTrigger(void)
 {
-	if (LocalPromoteIsTriggered)
+	if (localRecoveryFlags & SX_PROMOTE_IS_TRIGGERED)
 		return true;
 
@@ -4520,16 +4494,17 @@ HotStandbyActive(void)

@@ -4541,7 +4516,7 @@ static bool
 
 /*
@@ -5059,3 +5034,23 @@ assign_recovery_target_xid(const char *newval, void *extra)
 	else
index 2c0a7439be4..920040f3bcd 100644
 
index 2cf8d55d706..48c8cb85271 100644

+/*

-- 
2.34.1

From 33dbaa6cde687c97373959695c84c5eee72192a7 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <[email-masked]>
Date: Fri, 10 Jan 2025 22:31:34 +0300
Subject: [PATCH v5 2/2] Wrapper function to extract whole text array from
 recovery flags bitset.

index 3d1fe965a45..fb2c31616aa 100644
@@ -4370,6 +4370,22 @@ StartupRequestWalReceiverRestart(void)
 	}
 }
 
+/*
 
 /*
 
index cd9422d0bac..d8c5ec9d98a 100644
@@ -6685,6 +6685,11 @@
 
+{ oid => '8439',