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

Started by Nonamealmost 2 years ago15 messages
#1Noname
m.litsarev@postgrespro.ru
2 attachment(s)

Hi,

At present time, an existing pg_is_in_recovery() method is not enough
to distinguish a server being in point in time recovery (PITR) mode and
an ordinary replica
because it returns true in both cases.

That is why pg_is_standby_requested() function introduced in attached
patch might help.
It reports whether a standby.signal file was found in the data directory
at startup process.
Instructions for reproducing the possible use case are also attached.

Hope it will be usefull.

Respectfully,

Mikhail Litsarev
Postgres Professional: https://postgrespro.com

Attachments:

use_case_pitr.txttext/plain; name=use_case_pitr.txtDownload
v1-0001-Standby-mode-requested.patchtext/x-diff; name=v1-0001-Standby-mode-requested.patchDownload
From 563431ffb53d0b598f33d3378b7ba40338020ca6 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
Date: Tue, 20 Feb 2024 20:05:37 +0300
Subject: [PATCH 1/2] Introduce pg_is_standby_requested().

Introduce pg_is_standby_requested() function to distinguish
a replica from a regular instance in point in time recovery mode.
---
 src/backend/access/transam/xlogfuncs.c    | 14 ++++++++++
 src/backend/access/transam/xlogrecovery.c | 33 +++++++++++++++++++++++
 src/include/access/xlogrecovery.h         |  1 +
 src/include/catalog/pg_proc.dat           |  4 +++
 4 files changed, 52 insertions(+)

diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 45452d937c7..3170c4ef343 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -753,3 +753,17 @@ pg_promote(PG_FUNCTION_ARGS)
 						   wait_seconds)));
 	PG_RETURN_BOOL(false);
 }
+
+/*
+ * Returns bool with a current value of StandbyModeIsRequested flag
+ * to distinguish a replica from a regular instance in a
+ * Point In Time Recovery (PITR) mode.
+ *
+ * Returns	true	if standby.signal file is found at startup process
+ * 			false	otherwise
+ */
+Datum
+pg_is_standby_requested(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(StandbyModeIsRequested());
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 6318878356a..cbca4c5055d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -321,6 +321,12 @@ typedef struct XLogRecoveryCtlData
 	 */
 	bool		SharedPromoteIsTriggered;
 
+	/*
+	 * SharedStandbyModeRequested indicates if we're in a standby mode at
+	 * start, while recovery mode is on. Protected by info_lck.
+	 */
+	bool		SharedStandbyModeRequested;
+
 	/*
 	 * 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
@@ -1070,7 +1076,16 @@ readRecoverySignalFile(void)
 		ArchiveRecoveryRequested = true;
 	}
 	else
+	{
+		/*
+		 * There is no need to use Spinlock here because only the startup
+		 * process modifies the SharedStandbyModeRequested variable here and
+		 * no other processes are reading it at that time.
+		 */
+		XLogRecoveryCtl->SharedStandbyModeRequested = StandbyModeRequested;
 		return;
+	}
+	XLogRecoveryCtl->SharedStandbyModeRequested = StandbyModeRequested;
 
 	/*
 	 * We don't support standby mode in standalone backends; that requires
@@ -4555,6 +4570,24 @@ HotStandbyActiveInReplay(void)
 	return LocalHotStandbyActive;
 }
 
+
+/*
+ * It reports wether a standby.signal file is in the data directory
+ * at startup.
+ *
+ * This works in any process that's connected to shared memory.
+ */
+bool
+StandbyModeIsRequested(void)
+{
+	/*
+	 * Spinlock is not needed here because SharedStandbyModeRequested variable
+	 * can only be read after startup process is done and no other actions (in
+	 * functions, in processes) change it then.
+	 */
+	return XLogRecoveryCtl->SharedStandbyModeRequested;
+}
+
 /*
  * Get latest redo apply position.
  *
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 66affd2eac3..f85a2af6a23 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -145,6 +145,7 @@ extern TimestampTz GetLatestXTime(void);
 extern TimestampTz GetCurrentChunkReplayStartTime(void);
 extern XLogRecPtr GetCurrentReplayRecPtr(TimeLineID *replayEndTLI);
 
+extern bool StandbyModeIsRequested(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 bb775ce7264..a2ac4160d8c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6494,6 +6494,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 => 'Is StandbyMode requested at startup while being in recovery mode',
+  proname => 'pg_is_standby_requested', provolatile => 'v', prorettype => 'bool',
+  proargtypes => '', prosrc => 'pg_is_standby_requested' },
 
 { oid => '3820', descr => 'current wal flush location',
   proname => 'pg_last_wal_receive_lsn', provolatile => 'v',
-- 
2.34.1


From 3d944423ed1c0f2a73e4a38a61ac545f0450a70d Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
Date: Tue, 27 Feb 2024 14:08:42 +0300
Subject: [PATCH 2/2] Test pg_is_standby_requested() in different use-cases.

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 | 23 ++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl
index edaef918454..e3f320ab9ab 100644
--- a/src/test/recovery/t/004_timeline_switch.pl
+++ b/src/test/recovery/t/004_timeline_switch.pl
@@ -18,6 +18,11 @@ my $node_primary = PostgreSQL::Test::Cluster->new('primary');
 $node_primary->init(allows_streaming => 1);
 $node_primary->start;
 
+# Validate pg_is_standby_requested() for master
+my $ret_mode_primary = $node_primary->safe_psql('postgres',
+	'SELECT pg_is_standby_requested()');
+is($ret_mode_primary, 'f', "master is not a replica");
+
 # Take backup
 my $backup_name = 'my_backup';
 $node_primary->backup($backup_name);
@@ -32,6 +37,11 @@ $node_standby_2->init_from_backup($node_primary, $backup_name,
 	has_streaming => 1);
 $node_standby_2->start;
 
+# Validate pg_is_standby_requested() for replica node
+my $ret_mode_standby_1 = $node_standby_1->safe_psql('postgres',
+	'SELECT pg_is_standby_requested()');
+is($ret_mode_standby_1, 't', "node_standby_1 is a replica");
+
 # Create some content on primary
 $node_primary->safe_psql('postgres',
 	"CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
@@ -50,6 +60,14 @@ $node_standby_1->psql(
 	stdout => \$psql_out);
 is($psql_out, 't', "promotion of standby with pg_promote");
 
+# Validate pg_is_standby_requested() for master promoted from standby node.
+# pg_is_standby_requested() returns true because standby.signal file
+# was found while being a replica.
+# Use it with pg_is_in_recovery() (returns false), for such use-cases.
+my $ret_mode_1 = $node_standby_1->safe_psql('postgres',
+	'SELECT pg_is_standby_requested()');
+is($ret_mode_1, 't', "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(
@@ -58,6 +76,11 @@ primary_conninfo='$connstr_1'
 ));
 $node_standby_2->restart;
 
+# Validate pg_is_standby_requested() for second replica after restart
+my $ret_mode_standby_2 = $node_standby_2->safe_psql('postgres',
+	'SELECT pg_is_standby_requested()');
+is($ret_mode_standby_2, 't', "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

#2Tristan Partin
tristan@neon.tech
In reply to: Noname (#1)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

On Tue Mar 26, 2024 at 9:28 AM CDT, m.litsarev wrote:

Hi,

At present time, an existing pg_is_in_recovery() method is not enough
to distinguish a server being in point in time recovery (PITR) mode and
an ordinary replica
because it returns true in both cases.

That is why pg_is_standby_requested() function introduced in attached
patch might help.
It reports whether a standby.signal file was found in the data directory
at startup process.
Instructions for reproducing the possible use case are also attached.

Hope it will be usefull.

Hey Mikhail,

Saw your patch for the first time today. Looks like your patch is messed
up? You seem to have more of the diff at the bottom which seems to add
a test. Want to send a v2 with a properly formatted patch?

Example command:

git format-patch -v2 -M HEAD^

--
Tristan Partin
Neon (https://neon.tech)

#3Michael Paquier
michael@paquier.xyz
In reply to: Tristan Partin (#2)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

On Mon, Apr 15, 2024 at 04:06:03PM -0500, Tristan Partin wrote:

Saw your patch for the first time today. Looks like your patch is messed up?
You seem to have more of the diff at the bottom which seems to add a test.
Want to send a v2 with a properly formatted patch?

FWIW, complicating more XLogRecoveryCtlData sends me shivers, these
days, because we have already a lot of recovery state to track within
it.

More seriously, I'm not much a fan of introducing more branches at the
bottom of readRecoverySignalFile() for the boolean flags tracking if
standby and/or archive recovery are triggered, even if these are
simple there are already too many of them. Perhaps we should begin
tracking all that as a set of bitmasks, then plug in the tracked state
in shmem for consumption in some SQL function.
--
Michael

#4Noname
m.litsarev@postgrespro.ru
In reply to: Michael Paquier (#3)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

On 2024-Apr-16, Michael Paquier wrote:

there are already too many of them. Perhaps we should begin
tracking all that as a set of bitmasks, then plug in the tracked state
in shmem for consumption in some SQL function.

Yes, it sounds reasonable.
Let me implement some initial draft and come back with it after a while.

Respectfully,

Mikhail Litsarev
Postgres Professional: https://postgrespro.com

#5Noname
m.litsarev@postgrespro.ru
In reply to: Michael Paquier (#3)
1 attachment(s)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

simple there are already too many of them. Perhaps we should begin
tracking all that as a set of bitmasks, then plug in the tracked state
in shmem for consumption in some SQL function.

Hi!

Michael, Tristan
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.

Respectfully,

Mikhail Litsarev
Postgres Professional: https://postgrespro.com

Attachments:

v1-0001-Standby-mode-requested-bitmask.patchtext/x-diff; name=v1-0001-Standby-mode-requested-bitmask.patchDownload
From e05e86baf90a14efec29b0efcb5696504796af9b Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
Date: Mon, 6 May 2024 15:23:52 +0300
Subject: [PATCH 1/2] Introduce pg_is_standby_requested().

Introduce a bit array SharedRecoveryDataFlags and
pg_is_standby_requested() function to distinguish
a replica from a regular instance in point in time recovery mode.
---
 src/backend/access/transam/xlogfuncs.c    | 14 +++++
 src/backend/access/transam/xlogrecovery.c | 75 ++++++++++++++++++-----
 src/include/access/xlogrecovery.h         |  1 +
 src/include/catalog/pg_proc.dat           |  4 ++
 4 files changed, 79 insertions(+), 15 deletions(-)

diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 92bdb17ed52..3ce934fcedb 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -747,3 +747,17 @@ pg_promote(PG_FUNCTION_ARGS)
 						   wait_seconds)));
 	PG_RETURN_BOOL(false);
 }
+
+/*
+ * Returns bool with a current value of StandbyModeIsRequested flag
+ * to distinguish a replica from a regular instance in a
+ * Point In Time Recovery (PITR) mode.
+ *
+ * Returns	true	if standby.signal file is found at startup process
+ * 			false	otherwise
+ */
+Datum
+pg_is_standby_requested(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(StandbyModeIsRequested());
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 29c5bec0847..5337c4974f0 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -170,13 +170,13 @@ static XLogRecPtr RedoStartLSN = InvalidXLogRecPtr;
 static TimeLineID RedoStartTLI = 0;
 
 /*
- * 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;
@@ -298,22 +298,42 @@ static char *replay_image_masked = NULL;
 static char *primary_image_masked = NULL;
 
 
+/*
+ * 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.
+ */
+#define	XLR_STANDBY_MODE_REQUESTED	0x04
+
 /*
  * 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;
+	uint32		SharedRecoveryDataFlags;
 
 	/*
 	 * recoveryWakeupLatch is used to wake up the startup process to continue
@@ -1082,10 +1102,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)
 	{
@@ -2259,7 +2286,7 @@ CheckRecoveryConsistency(void)
 		IsUnderPostmaster)
 	{
 		SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-		XLogRecoveryCtl->SharedHotStandbyActive = true;
+		XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_HOT_STANDBY_ACTIVE;
 		SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
 		LocalHotStandbyActive = true;
@@ -4402,7 +4429,8 @@ PromoteIsTriggered(void)
 		return true;
 
 	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-	LocalPromoteIsTriggered = XLogRecoveryCtl->SharedPromoteIsTriggered;
+	LocalPromoteIsTriggered =
+		(XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED) != 0;
 	SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
 	return LocalPromoteIsTriggered;
@@ -4412,7 +4440,7 @@ static void
 SetPromoteIsTriggered(void)
 {
 	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-	XLogRecoveryCtl->SharedPromoteIsTriggered = true;
+	XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_PROMOTE_IS_TRIGGERED;
 	SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
 	/*
@@ -4512,7 +4540,8 @@ HotStandbyActive(void)
 	{
 		/* spinlock is essential on machines with weak memory ordering! */
 		SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-		LocalHotStandbyActive = XLogRecoveryCtl->SharedHotStandbyActive;
+		LocalHotStandbyActive =
+			(XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE) != 0;
 		SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
 		return LocalHotStandbyActive;
@@ -4530,6 +4559,22 @@ HotStandbyActiveInReplay(void)
 	return LocalHotStandbyActive;
 }
 
+/*
+ * It reports whether a standby.signal file is in the data directory
+ * at startup.
+ *
+ * This works in any process that's connected to shared memory.
+ */
+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;
+}
+
 /*
  * Get latest redo apply position.
  *
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index c423464e8bc..ad94dd79629 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -144,6 +144,7 @@ extern TimestampTz GetLatestXTime(void);
 extern TimestampTz GetCurrentChunkReplayStartTime(void);
 extern XLogRecPtr GetCurrentReplayRecPtr(TimeLineID *replayEndTLI);
 
+extern bool StandbyModeIsRequested(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 134e3b22fd8..9bf97f81d61 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6519,6 +6519,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 => 'Is StandbyMode requested at startup while being in recovery mode',
+  proname => 'pg_is_standby_requested', provolatile => 'v', prorettype => 'bool',
+  proargtypes => '', prosrc => 'pg_is_standby_requested' },
 
 { oid => '3820', descr => 'current wal flush location',
   proname => 'pg_last_wal_receive_lsn', provolatile => 'v',
-- 
2.34.1


From 9e2be31c38987b565fe2b41b5470659911cf2a10 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
Date: Tue, 27 Feb 2024 14:08:42 +0300
Subject: [PATCH 2/2] Test pg_is_standby_requested() in different use-cases.

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 | 23 ++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl
index 684838cbab7..10a6fa8b905 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_is_standby_requested() for master
+my $ret_mode_primary = $node_primary->safe_psql('postgres',
+	'SELECT pg_is_standby_requested()');
+is($ret_mode_primary, 'f', "master is not a replica");
+
 # Take backup
 my $backup_name = 'my_backup';
 $node_primary->backup($backup_name);
@@ -30,6 +35,11 @@ $node_standby_2->init_from_backup($node_primary, $backup_name,
 	has_streaming => 1);
 $node_standby_2->start;
 
+# Validate pg_is_standby_requested() for replica node
+my $ret_mode_standby_1 = $node_standby_1->safe_psql('postgres',
+	'SELECT pg_is_standby_requested()');
+is($ret_mode_standby_1, 't', "node_standby_1 is a replica");
+
 # Create some content on primary
 $node_primary->safe_psql('postgres',
 	"CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
@@ -48,6 +58,14 @@ $node_standby_1->psql(
 	stdout => \$psql_out);
 is($psql_out, 't', "promotion of standby with pg_promote");
 
+# Validate pg_is_standby_requested() for master promoted from standby node.
+# pg_is_standby_requested() returns true because standby.signal file
+# was found while being a replica.
+# Use it with pg_is_in_recovery() (returns false), for such use-cases.
+my $ret_mode_1 = $node_standby_1->safe_psql('postgres',
+	'SELECT pg_is_standby_requested()');
+is($ret_mode_1, 't', "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 +74,11 @@ primary_conninfo='$connstr_1'
 ));
 $node_standby_2->restart;
 
+# Validate pg_is_standby_requested() for second replica after restart
+my $ret_mode_standby_2 = $node_standby_2->safe_psql('postgres',
+	'SELECT pg_is_standby_requested()');
+is($ret_mode_standby_2, 't', "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

#6Michael Paquier
michael@paquier.xyz
In reply to: Noname (#5)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

On Mon, May 06, 2024 at 06:55:46PM +0300, m.litsarev@postgrespro.ru 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

#7Noname
m.litsarev@postgrespro.ru
In reply to: Michael Paquier (#6)
3 attachment(s)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

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
text[] array with all the states retrieved?

Placed this in the v3-0002-Text-array-sql-wrapper.patch

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

Sure. I also added the third patch with some tests. Perhaps it would be
usefull.

Respectfully,

Mikhail Litsarev
Postgres Professional: https://postgrespro.com

Attachments:

v3-0003-Test-standby-is-requested.patchtext/x-diff; name=v3-0003-Test-standby-is-requested.patchDownload
From cd32de9ec14dba0011f8c05e6a8ab7e4f7be045c Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
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

v3-0002-Text-array-sql-wrapper.patchtext/x-diff; name=v3-0002-Text-array-sql-wrapper.patchDownload
From ac304f75d5f6f9bb9712ef4113da035ad74b7344 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
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

v3-0001-Standby-mode-requested.patchtext/x-diff; name=v3-0001-Standby-mode-requested.patchDownload
From 23e23dd4c0b1c4c69021a4f2bf95586f39d8bd1f Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
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

#8Michael Paquier
michael@paquier.xyz
In reply to: Noname (#7)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

On Thu, Jun 13, 2024 at 09:07:42PM +0300, m.litsarev@postgrespro.ru wrote:

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
text[] array with all the states retrieved?

Placed this in the v3-0002-Text-array-sql-wrapper.patch

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

Sure. I also added the third patch with some tests. Perhaps it would be
usefull.

+     *    --    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.

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.

StandbyModeRequested = true;
ArchiveRecoveryRequested = true;
+ XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_STANDBY_MODE_REQUESTED;

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..

return (LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE);
}

+
/*
Some noise lying around.

+	/* Returns bit array as Datum */
+	txt_arr = construct_array_builtin(flags, cnt, TEXTOID);

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

+is($ret_mode_primary, '{}', "master is not a replica");

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

#9Noname
m.litsarev@postgrespro.ru
In reply to: Michael Paquier (#8)
2 attachment(s)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

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

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..

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,
Postgres Professional: https://postgrespro.com

Attachments:

v4-0001-Standby-mode-requested.patchtext/x-diff; name=v4-0001-Standby-mode-requested.patchDownload
From eaf661c022f82217d33cfdb3776c69675f82ac67 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
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;
 
 /* Has the recovery code requested a walreceiver wakeup? */
 static bool doRequestWalReceiverReply;
@@ -297,23 +277,16 @@ 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.
+	 * 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
 SetPromoteIsTriggered(void)
 {
 	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-	XLogRecoveryCtl->SharedPromoteIsTriggered = true;
+	XLogRecoveryCtl->sharedRecoveryFlags |= SX_PROMOTE_IS_TRIGGERED;
 	SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
 	/*
@@ -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

v4-0002-Text-array-sql-wrapper.patchtext/x-diff; name=v4-0002-Text-array-sql-wrapper.patchDownload
From 39c42bdcaa371ea356ff2db8944d4cf8535d23ba Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
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);
 
+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 b37e8a6f882..e1e10f6a5d6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6660,6 +6660,11 @@
   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',
   prorettype => 'pg_lsn', proargtypes => '',
-- 
2.34.1

#10Noname
m.litsarev@postgrespro.ru
In reply to: Noname (#9)
2 attachment(s)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

Hi!

Rebased the patch.

Respectfully,

Mikhail Litsarev,
Postgres Professional: https://postgrespro.com

Attachments:

v5-0001-Replace-recovery-boolean-flags-with-a-bits32-set.patchtext/x-diff; name=v5-0001-Replace-recovery-boolean-flags-with-a-bits32-set.patchDownload
From 67b190ce15f8ba7480ba1691b804b27f96fd5540 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
Date: Fri, 10 Jan 2025 21:23:02 +0300
Subject: [PATCH v5 1/2] 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 the 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 799fc739e18..f3af732b229 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -5434,7 +5434,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),
@@ -5447,7 +5447,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",
@@ -5607,8 +5607,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);
@@ -5740,7 +5740,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;
@@ -5793,7 +5793,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;
@@ -5827,7 +5827,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;
@@ -5940,7 +5940,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,
@@ -5992,7 +5992,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,
@@ -6185,7 +6185,7 @@ StartupXLOG(void)
 	XLogReportParameters();
 
 	/* If this is archive recovery, perform post-recovery cleanup actions. */
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog, newTLI);
 
 	/*
@@ -6344,7 +6344,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;
@@ -8338,7 +8338,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,
@@ -8579,7 +8579,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 52f53fa12e0..3d1fe965a45 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -124,30 +124,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;
 
@@ -171,16 +148,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;
 
 /* Has the recovery code requested a walreceiver wakeup? */
 static bool doRequestWalReceiverReply;
@@ -298,23 +278,16 @@ 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.
+	 * 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
@@ -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;
 
 	/*
 	 * To avoid server log bloat, we don't report recovery progress in a
@@ -506,8 +480,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,
@@ -545,7 +519,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);
 
 	/*
@@ -600,8 +574,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();
 
 		/*
@@ -750,14 +724,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();
 		}
 
@@ -800,9 +774,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)
@@ -914,7 +888,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;
@@ -931,7 +905,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 	 */
 	if (InRecovery)
 	{
-		if (InArchiveRecovery)
+		if (InArchiveRecovery())
 		{
 			ControlFile->state = DB_IN_ARCHIVE_RECOVERY;
 		}
@@ -950,7 +924,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)
@@ -997,7 +971,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;
@@ -1083,17 +1057,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;
@@ -1102,7 +1075,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")));
@@ -1111,13 +1084,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))
@@ -1158,8 +1131,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)
 	{
@@ -1497,7 +1470,7 @@ FinishWalRecovery(void)
 	 * i.e., calling XLogShutdownWalRcv().
 	 */
 	Assert(!WalRcvStreaming());
-	StandbyMode = false;
+	localRecoveryFlags &= ~SX_IN_STANDBY_MODE;
 
 	/*
 	 * Determine where to start writing WAL next.
@@ -1535,7 +1508,7 @@ FinishWalRecovery(void)
 	 */
 	result->endOfLogTLI = xlogreader->seg.ws_tli;
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		/*
 		 * We are no longer in archive recovery state.
@@ -1543,8 +1516,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
@@ -1624,7 +1597,7 @@ ShutdownWalRecovery(void)
 	XLogReaderFree(xlogreader);
 	XLogPrefetcherFree(xlogprefetcher);
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		/*
 		 * Since there might be a partial WAL segment named RECOVERYXLOG, get
@@ -1642,7 +1615,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);
 }
 
@@ -1744,7 +1717,7 @@ PerformWalRecovery(void)
 						LSN_FORMAT_ARGS(xlogreader->ReadRecPtr))));
 
 		/* Prepare to report progress of the redo phase. */
-		if (!StandbyMode)
+		if (!InStandbyMode())
 			begin_startup_progress_phase();
 
 		/*
@@ -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;
 
-	Assert(InArchiveRecovery);
+	Assert(InArchiveRecovery());
 
 	/*
 	 * assume that we are called in the startup process, and hence don't need
@@ -2259,15 +2232,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);
 	}
@@ -2587,7 +2560,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 */
@@ -2739,7 +2712,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;
@@ -2930,11 +2903,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)
@@ -3000,7 +2973,7 @@ recoveryApplyDelay(XLogReaderState *record)
 		return false;
 
 	/* nothing to do if crash recovery is requested */
-	if (!ArchiveRecoveryRequested)
+	if (!ArchiveRecoveryRequested())
 		return false;
 
 	/*
@@ -3162,13 +3135,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;
@@ -3237,13 +3210,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);
@@ -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;
 
 	/* In standby-mode, keep trying */
-	if (StandbyMode)
+	if (InStandbyMode())
 		goto retry;
 	else
 		return XLREAD_FAIL;
@@ -3590,10 +3563,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;
@@ -3631,7 +3604,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;
@@ -3641,7 +3614,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;
 
 					/*
@@ -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());
 
 					/*
 					 * Before we leave XLOG_FROM_STREAM state, make sure that
@@ -3744,7 +3717,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;
 		}
 
@@ -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());
 
 					/*
 					 * First, shutdown walreceiver if its restart has been
@@ -4412,21 +4385,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
 SetPromoteIsTriggered(void)
 {
 	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-	XLogRecoveryCtl->SharedPromoteIsTriggered = true;
+	XLogRecoveryCtl->sharedRecoveryFlags |= SX_PROMOTE_IS_TRIGGERED;
 	SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
 	/*
@@ -4437,7 +4411,7 @@ SetPromoteIsTriggered(void)
 	 */
 	SetRecoveryPause(false);
 
-	LocalPromoteIsTriggered = true;
+	localRecoveryFlags |= SX_PROMOTE_IS_TRIGGERED;
 }
 
 /*
@@ -4446,7 +4420,7 @@ SetPromoteIsTriggered(void)
 static bool
 CheckForStandbyTrigger(void)
 {
-	if (LocalPromoteIsTriggered)
+	if (localRecoveryFlags & SX_PROMOTE_IS_TRIGGERED)
 		return true;
 
 	if (IsPromoteSignaled() && CheckPromoteSignal())
@@ -4520,16 +4494,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;
 	}
 }
 
@@ -4541,7 +4516,7 @@ static bool
 HotStandbyActiveInReplay(void)
 {
 	Assert(AmStartupProcess() || !IsPostmasterEnvironment);
-	return LocalHotStandbyActive;
+	return localRecoveryFlags & SX_HOT_STANDBY_ACTIVE;
 }
 
 /*
@@ -5059,3 +5034,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 2c0a7439be4..920040f3bcd 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 2cf8d55d706..48c8cb85271 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

v5-0002-Wrapper-function-to-extract-whole-text-array-from.patchtext/x-diff; name=v5-0002-Wrapper-function-to-extract-whole-text-array-from.patchDownload
From 33dbaa6cde687c97373959695c84c5eee72192a7 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
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.

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 3d1fe965a45..fb2c31616aa 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4370,6 +4370,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);
 
+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 cd9422d0bac..d8c5ec9d98a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6685,6 +6685,11 @@
   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',
   prorettype => 'pg_lsn', proargtypes => '',
-- 
2.34.1

#11Noname
m.litsarev@postgrespro.ru
In reply to: Noname (#10)
2 attachment(s)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

Hi,

Fix an error in the patch.

Respectfully,

Mikhail Litsarev,
Postgres Professional: https://postgrespro.com

Attachments:

v6-0001-Replace-recovery-boolean-flags-with-a-bits32-set.patchtext/x-diff; name=v6-0001-Replace-recovery-boolean-flags-with-a-bits32-set.patchDownload
From d96d322e9c146e35ef1a5c3168109d3b059585f7 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
Date: Fri, 10 Jan 2025 21:23:02 +0300
Subject: [PATCH v6 1/2] 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 the 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/backend/replication/slot.c             |   2 +-
 src/include/access/xlog_internal.h         |   7 +-
 src/include/access/xlogrecovery.h          |  34 +++-
 8 files changed, 162 insertions(+), 140 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 799fc739e18..f3af732b229 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -5434,7 +5434,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),
@@ -5447,7 +5447,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",
@@ -5607,8 +5607,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);
@@ -5740,7 +5740,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;
@@ -5793,7 +5793,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;
@@ -5827,7 +5827,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;
@@ -5940,7 +5940,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,
@@ -5992,7 +5992,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,
@@ -6185,7 +6185,7 @@ StartupXLOG(void)
 	XLogReportParameters();
 
 	/* If this is archive recovery, perform post-recovery cleanup actions. */
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog, newTLI);
 
 	/*
@@ -6344,7 +6344,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;
@@ -8338,7 +8338,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,
@@ -8579,7 +8579,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 52f53fa12e0..3d1fe965a45 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -124,30 +124,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;
 
@@ -171,16 +148,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;
 
 /* Has the recovery code requested a walreceiver wakeup? */
 static bool doRequestWalReceiverReply;
@@ -298,23 +278,16 @@ 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.
+	 * 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
@@ -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;
 
 	/*
 	 * To avoid server log bloat, we don't report recovery progress in a
@@ -506,8 +480,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,
@@ -545,7 +519,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);
 
 	/*
@@ -600,8 +574,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();
 
 		/*
@@ -750,14 +724,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();
 		}
 
@@ -800,9 +774,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)
@@ -914,7 +888,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;
@@ -931,7 +905,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 	 */
 	if (InRecovery)
 	{
-		if (InArchiveRecovery)
+		if (InArchiveRecovery())
 		{
 			ControlFile->state = DB_IN_ARCHIVE_RECOVERY;
 		}
@@ -950,7 +924,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)
@@ -997,7 +971,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;
@@ -1083,17 +1057,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;
@@ -1102,7 +1075,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")));
@@ -1111,13 +1084,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))
@@ -1158,8 +1131,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)
 	{
@@ -1497,7 +1470,7 @@ FinishWalRecovery(void)
 	 * i.e., calling XLogShutdownWalRcv().
 	 */
 	Assert(!WalRcvStreaming());
-	StandbyMode = false;
+	localRecoveryFlags &= ~SX_IN_STANDBY_MODE;
 
 	/*
 	 * Determine where to start writing WAL next.
@@ -1535,7 +1508,7 @@ FinishWalRecovery(void)
 	 */
 	result->endOfLogTLI = xlogreader->seg.ws_tli;
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		/*
 		 * We are no longer in archive recovery state.
@@ -1543,8 +1516,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
@@ -1624,7 +1597,7 @@ ShutdownWalRecovery(void)
 	XLogReaderFree(xlogreader);
 	XLogPrefetcherFree(xlogprefetcher);
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		/*
 		 * Since there might be a partial WAL segment named RECOVERYXLOG, get
@@ -1642,7 +1615,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);
 }
 
@@ -1744,7 +1717,7 @@ PerformWalRecovery(void)
 						LSN_FORMAT_ARGS(xlogreader->ReadRecPtr))));
 
 		/* Prepare to report progress of the redo phase. */
-		if (!StandbyMode)
+		if (!InStandbyMode())
 			begin_startup_progress_phase();
 
 		/*
@@ -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;
 
-	Assert(InArchiveRecovery);
+	Assert(InArchiveRecovery());
 
 	/*
 	 * assume that we are called in the startup process, and hence don't need
@@ -2259,15 +2232,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);
 	}
@@ -2587,7 +2560,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 */
@@ -2739,7 +2712,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;
@@ -2930,11 +2903,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)
@@ -3000,7 +2973,7 @@ recoveryApplyDelay(XLogReaderState *record)
 		return false;
 
 	/* nothing to do if crash recovery is requested */
-	if (!ArchiveRecoveryRequested)
+	if (!ArchiveRecoveryRequested())
 		return false;
 
 	/*
@@ -3162,13 +3135,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;
@@ -3237,13 +3210,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);
@@ -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;
 
 	/* In standby-mode, keep trying */
-	if (StandbyMode)
+	if (InStandbyMode())
 		goto retry;
 	else
 		return XLREAD_FAIL;
@@ -3590,10 +3563,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;
@@ -3631,7 +3604,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;
@@ -3641,7 +3614,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;
 
 					/*
@@ -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());
 
 					/*
 					 * Before we leave XLOG_FROM_STREAM state, make sure that
@@ -3744,7 +3717,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;
 		}
 
@@ -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());
 
 					/*
 					 * First, shutdown walreceiver if its restart has been
@@ -4412,21 +4385,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
 SetPromoteIsTriggered(void)
 {
 	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-	XLogRecoveryCtl->SharedPromoteIsTriggered = true;
+	XLogRecoveryCtl->sharedRecoveryFlags |= SX_PROMOTE_IS_TRIGGERED;
 	SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
 	/*
@@ -4437,7 +4411,7 @@ SetPromoteIsTriggered(void)
 	 */
 	SetRecoveryPause(false);
 
-	LocalPromoteIsTriggered = true;
+	localRecoveryFlags |= SX_PROMOTE_IS_TRIGGERED;
 }
 
 /*
@@ -4446,7 +4420,7 @@ SetPromoteIsTriggered(void)
 static bool
 CheckForStandbyTrigger(void)
 {
-	if (LocalPromoteIsTriggered)
+	if (localRecoveryFlags & SX_PROMOTE_IS_TRIGGERED)
 		return true;
 
 	if (IsPromoteSignaled() && CheckPromoteSignal())
@@ -4520,16 +4494,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;
 	}
 }
 
@@ -4541,7 +4516,7 @@ static bool
 HotStandbyActiveInReplay(void)
 {
 	Assert(AmStartupProcess() || !IsPostmasterEnvironment);
-	return LocalHotStandbyActive;
+	return localRecoveryFlags & SX_HOT_STANDBY_ACTIVE;
 }
 
 /*
@@ -5059,3 +5034,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 2c0a7439be4..920040f3bcd 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/backend/replication/slot.c b/src/backend/replication/slot.c
index 719e531eb90..e4b82304d18 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -2537,7 +2537,7 @@ RestoreSlotFromDisk(const char *name)
 		 * primary reduces wal_level < logical while hot standby is disabled,
 		 * logical slots would remain valid even after promotion.
 		 */
-		if (StandbyMode && !EnableHotStandby)
+		if (InStandbyMode() && !EnableHotStandby)
 			ereport(FATAL,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("logical replication slot \"%s\" exists on the standby, but \"hot_standby\" = \"off\"",
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 2cf8d55d706..48c8cb85271 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

v6-0002-Wrapper-function-to-extract-whole-text-array-from.patchtext/x-diff; name=v6-0002-Wrapper-function-to-extract-whole-text-array-from.patchDownload
From 37cda666a722dab5b08154a3e400dce23c9f7a60 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
Date: Fri, 10 Jan 2025 22:31:34 +0300
Subject: [PATCH v6 2/2] 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 3d1fe965a45..fb2c31616aa 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4370,6 +4370,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);
 
+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 cd9422d0bac..d8c5ec9d98a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6685,6 +6685,11 @@
   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',
   prorettype => 'pg_lsn', proargtypes => '',
-- 
2.34.1

#12vignesh C
vignesh21@gmail.com
In reply to: Noname (#11)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

On Fri, 28 Feb 2025 at 19:37, <m.litsarev@postgrespro.ru> wrote:

Hi,

Fix an error in the patch.

I felt you might have missed attaching the test patches added at [1]/messages/by-id/4ba66566b84df983c881b996eb8831f1@postgrespro.ru.
Also the test from [1]/messages/by-id/4ba66566b84df983c881b996eb8831f1@postgrespro.ru is failing with the latest v6 version patch.

This change is not required:
extern void XLogRecGetBlockRefInfo(XLogReaderState *record, bool pretty,

bool detailed_format, StringInfo buf,

uint32 *fpi_len);
-
/*

[1]: /messages/by-id/4ba66566b84df983c881b996eb8831f1@postgrespro.ru

Regards,
Vignesh

#13vignesh C
vignesh21@gmail.com
In reply to: Noname (#11)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

On Fri, 28 Feb 2025 at 19:37, <m.litsarev@postgrespro.ru> wrote:

Hi,

Fix an error in the patch.

Currently we have the following commitfest entries for this thread:
[1]: https://commitfest.postgresql.org/patch/5611/
[2]: https://commitfest.postgresql.org/patch/5513/

I have closed the second entry at [2]https://commitfest.postgresql.org/patch/5513/.

Regards,
Vignesh

#14Noname
m.litsarev@postgrespro.ru
In reply to: vignesh C (#12)
2 attachment(s)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

Hi!

I felt you might have missed attaching the test patches added at [1].

Well, the tests were written for the initial proposal which (after
Michael's review and advices) has been fixed and updated. The original
tests became not relevant actually. That is why I dropped them.

This change is not required:

Placed back the empty line. The v7 patch is attached.

Currently we have the following commitfest entries for this thread:
[1] - https://commitfest.postgresql.org/patch/5611/
[2] - https://commitfest.postgresql.org/patch/5513/
I have closed the second entry at [2].

I mistakenly pushed the patch twice. Thank you for managing that.

Respectfully,

Mikhail Litsarev,
Postgres Professional: https://postgrespro.com

Attachments:

v7-0002-Wrapper-function-to-extract-whole-text-array-from.patchtext/x-diff; name=v7-0002-Wrapper-function-to-extract-whole-text-array-from.patchDownload
From 0e7f61589c282a2e9b82cdbf8eadc2159301c3e8 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
Date: Tue, 25 Mar 2025 23:02:23 +0300
Subject: [PATCH v7 2/2] 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 9d8c1cdde43..bd5bdeb5db8 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4370,6 +4370,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);
 
+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 3f7b82e02bb..8b775f0bbd9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6714,6 +6714,11 @@
   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',
   prorettype => 'pg_lsn', proargtypes => '',
-- 
2.34.1

v7-0001-Replace-recovery-boolean-flags-with-a-bits32-set.patchtext/x-diff; name=v7-0001-Replace-recovery-boolean-flags-with-a-bits32-set.patchDownload
From 1667c30af357943036359139c1f3442f576413d4 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsarev@postgrespro.ru>
Date: Tue, 25 Mar 2025 22:56:09 +0300
Subject: [PATCH v7 1/2] 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 the 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/backend/replication/slot.c             |   2 +-
 src/include/access/xlog_internal.h         |   6 +-
 src/include/access/xlogrecovery.h          |  34 +++-
 8 files changed, 162 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 4b6c694a3f7..6fb13a8a769 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -5434,7 +5434,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),
@@ -5447,7 +5447,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",
@@ -5607,8 +5607,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);
@@ -5740,7 +5740,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;
@@ -5793,7 +5793,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;
@@ -5827,7 +5827,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;
@@ -5940,7 +5940,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,
@@ -5992,7 +5992,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,
@@ -6185,7 +6185,7 @@ StartupXLOG(void)
 	XLogReportParameters();
 
 	/* If this is archive recovery, perform post-recovery cleanup actions. */
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog, newTLI);
 
 	/*
@@ -6344,7 +6344,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;
@@ -8338,7 +8338,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,
@@ -8579,7 +8579,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 2c19013c98b..9d8c1cdde43 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -124,30 +124,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;
 
@@ -171,16 +148,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;
 
 /* Has the recovery code requested a walreceiver wakeup? */
 static bool doRequestWalReceiverReply;
@@ -298,23 +278,16 @@ 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.
+	 * 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
@@ -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;
 
 	/*
 	 * To avoid server log bloat, we don't report recovery progress in a
@@ -506,8 +480,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,
@@ -545,7 +519,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);
 
 	/*
@@ -600,8 +574,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();
 
 		/*
@@ -750,14 +724,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();
 		}
 
@@ -800,9 +774,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)
@@ -914,7 +888,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;
@@ -931,7 +905,7 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 	 */
 	if (InRecovery)
 	{
-		if (InArchiveRecovery)
+		if (InArchiveRecovery())
 		{
 			ControlFile->state = DB_IN_ARCHIVE_RECOVERY;
 		}
@@ -950,7 +924,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)
@@ -997,7 +971,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;
@@ -1083,17 +1057,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;
@@ -1102,7 +1075,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")));
@@ -1111,13 +1084,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))
@@ -1158,8 +1131,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)
 	{
@@ -1497,7 +1470,7 @@ FinishWalRecovery(void)
 	 * i.e., calling XLogShutdownWalRcv().
 	 */
 	Assert(!WalRcvStreaming());
-	StandbyMode = false;
+	localRecoveryFlags &= ~SX_IN_STANDBY_MODE;
 
 	/*
 	 * Determine where to start writing WAL next.
@@ -1535,7 +1508,7 @@ FinishWalRecovery(void)
 	 */
 	result->endOfLogTLI = xlogreader->seg.ws_tli;
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		/*
 		 * We are no longer in archive recovery state.
@@ -1543,8 +1516,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
@@ -1624,7 +1597,7 @@ ShutdownWalRecovery(void)
 	XLogReaderFree(xlogreader);
 	XLogPrefetcherFree(xlogprefetcher);
 
-	if (ArchiveRecoveryRequested)
+	if (ArchiveRecoveryRequested())
 	{
 		/*
 		 * Since there might be a partial WAL segment named RECOVERYXLOG, get
@@ -1642,7 +1615,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);
 }
 
@@ -1744,7 +1717,7 @@ PerformWalRecovery(void)
 						LSN_FORMAT_ARGS(xlogreader->ReadRecPtr))));
 
 		/* Prepare to report progress of the redo phase. */
-		if (!StandbyMode)
+		if (!InStandbyMode())
 			begin_startup_progress_phase();
 
 		/*
@@ -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;
 
-	Assert(InArchiveRecovery);
+	Assert(InArchiveRecovery());
 
 	/*
 	 * assume that we are called in the startup process, and hence don't need
@@ -2259,15 +2232,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);
 	}
@@ -2587,7 +2560,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 */
@@ -2739,7 +2712,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;
@@ -2930,11 +2903,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)
@@ -3000,7 +2973,7 @@ recoveryApplyDelay(XLogReaderState *record)
 		return false;
 
 	/* nothing to do if crash recovery is requested */
-	if (!ArchiveRecoveryRequested)
+	if (!ArchiveRecoveryRequested())
 		return false;
 
 	/*
@@ -3162,13 +3135,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;
@@ -3237,13 +3210,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);
@@ -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;
 
 	/* In standby-mode, keep trying */
-	if (StandbyMode)
+	if (InStandbyMode())
 		goto retry;
 	else
 		return XLREAD_FAIL;
@@ -3590,10 +3563,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;
@@ -3631,7 +3604,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;
@@ -3641,7 +3614,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;
 
 					/*
@@ -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());
 
 					/*
 					 * Before we leave XLOG_FROM_STREAM state, make sure that
@@ -3744,7 +3717,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;
 		}
 
@@ -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());
 
 					/*
 					 * First, shutdown walreceiver if its restart has been
@@ -4412,21 +4385,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
 SetPromoteIsTriggered(void)
 {
 	SpinLockAcquire(&XLogRecoveryCtl->info_lck);
-	XLogRecoveryCtl->SharedPromoteIsTriggered = true;
+	XLogRecoveryCtl->sharedRecoveryFlags |= SX_PROMOTE_IS_TRIGGERED;
 	SpinLockRelease(&XLogRecoveryCtl->info_lck);
 
 	/*
@@ -4437,7 +4411,7 @@ SetPromoteIsTriggered(void)
 	 */
 	SetRecoveryPause(false);
 
-	LocalPromoteIsTriggered = true;
+	localRecoveryFlags |= SX_PROMOTE_IS_TRIGGERED;
 }
 
 /*
@@ -4446,7 +4420,7 @@ SetPromoteIsTriggered(void)
 static bool
 CheckForStandbyTrigger(void)
 {
-	if (LocalPromoteIsTriggered)
+	if (localRecoveryFlags & SX_PROMOTE_IS_TRIGGERED)
 		return true;
 
 	if (IsPromoteSignaled() && CheckPromoteSignal())
@@ -4520,16 +4494,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;
 	}
 }
 
@@ -4541,7 +4516,7 @@ static bool
 HotStandbyActiveInReplay(void)
 {
 	Assert(AmStartupProcess() || !IsPostmasterEnvironment);
-	return LocalHotStandbyActive;
+	return localRecoveryFlags & SX_HOT_STANDBY_ACTIVE;
 }
 
 /*
@@ -5058,3 +5033,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 2c0a7439be4..920040f3bcd 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/backend/replication/slot.c b/src/backend/replication/slot.c
index 719e531eb90..e4b82304d18 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -2537,7 +2537,7 @@ RestoreSlotFromDisk(const char *name)
 		 * primary reduces wal_level < logical while hot standby is disabled,
 		 * logical slots would remain valid even after promotion.
 		 */
-		if (StandbyMode && !EnableHotStandby)
+		if (InStandbyMode() && !EnableHotStandby)
 			ereport(FATAL,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("logical replication slot \"%s\" exists on the standby, but \"hot_standby\" = \"off\"",
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 2cf8d55d706..2fdf03e33cb 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -397,9 +397,9 @@ extern void XLogRecGetBlockRefInfo(XLogReaderState *record, bool pretty,
  * 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

#15Fujii Masao
masao.fujii@oss.nttdata.com
In reply to: Noname (#14)
Re: SQL function which allows to distinguish a server being in point in time recovery mode and an ordinary replica

On 2025/03/26 5:26, m.litsarev@postgrespro.ru wrote:

Hi!

I felt you might have missed attaching the test patches added at [1].

Well, the tests were written for the initial proposal which (after Michael's review and advices) has been fixed and updated. The original tests became not relevant actually. That is why I dropped them.

This change is not required:

Placed back the empty line. The v7 patch is attached.

Wouldn't pg_last_wal_receive_lsn() be almost sufficient for the purpose?
It generally returns NULL in archive recovery mode and a valid LSN
in standby mode. While it's not a perfect solution since it may return NULL
in standby mode until the WAL receiver starts, but it should work in most cases.
Thought?

Regards,

--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION