From bdf9a409005f0ea209c640935d9827369725e241 Mon Sep 17 00:00:00 2001
From: Elvis Pranskevichus <elvis@magic.io>
Date: Fri, 17 Mar 2017 13:25:08 -0400
Subject: [PATCH v1] Add and report the new "in_hot_standby" GUC
 pseudo-variable.

Currently, clients wishing to know when the server exits hot standby
have to resort to polling, which is suboptimal.

This adds the new "in_hot_standby" GUC variable that is reported via a
ParameterStatus message.  This allows the clients to:

   (a) know right away that they are connected to a server in hot
       standby; and

   (b) know immediately when a server exits hot standby.

This change will be most beneficial to various connection pooling
systems.
---
 doc/src/sgml/high-availability.sgml  |  4 +--
 doc/src/sgml/libpq.sgml              |  1 +
 doc/src/sgml/protocol.sgml           |  1 +
 doc/src/sgml/ref/show.sgml           |  9 +++++++
 src/backend/access/transam/xlog.c    |  4 ++-
 src/backend/commands/async.c         | 48 ++++++++++++++++++++++++++++++++++++
 src/backend/storage/ipc/procarray.c  | 30 ++++++++++++++++++++++
 src/backend/storage/ipc/procsignal.c |  3 +++
 src/backend/storage/ipc/standby.c    |  9 +++++++
 src/backend/tcop/postgres.c          |  6 ++++-
 src/backend/utils/init/postinit.c    |  6 +++++
 src/backend/utils/misc/check_guc     | 10 ++++----
 src/backend/utils/misc/guc.c         | 15 +++++++++++
 src/include/commands/async.h         |  7 ++++++
 src/include/storage/procarray.h      |  2 ++
 src/include/storage/procsignal.h     |  2 ++
 src/include/storage/standby.h        |  1 +
 17 files changed, 149 insertions(+), 9 deletions(-)

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index cc84b911b0..44795c5bcc 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1831,8 +1831,8 @@ if (!triggered)
    </para>
 
    <para>
-    Users will be able to tell whether their session is read-only by
-    issuing <command>SHOW transaction_read_only</>.  In addition, a set of
+    Users will be able to tell whether their session is in hot standby mode by
+    issuing <command>SHOW in_hot_standby</>.  In addition, a set of
     functions (<xref linkend="functions-recovery-info-table">) allow users to
     access information about the standby server. These allow you to write
     programs that are aware of the current state of the database. These
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 4bc5bf3192..367ec4460d 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1706,6 +1706,7 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
        <varname>server_encoding</>,
        <varname>client_encoding</>,
        <varname>application_name</>,
+       <varname>in_hot_standby</>,
        <varname>is_superuser</>,
        <varname>session_authorization</>,
        <varname>DateStyle</>,
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 7c82b48845..0fafaf6788 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1123,6 +1123,7 @@
     <varname>server_encoding</>,
     <varname>client_encoding</>,
     <varname>application_name</>,
+    <varname>in_hot_standby</>,
     <varname>is_superuser</>,
     <varname>session_authorization</>,
     <varname>DateStyle</>,
diff --git a/doc/src/sgml/ref/show.sgml b/doc/src/sgml/ref/show.sgml
index 46bb239baf..cf21bd961a 100644
--- a/doc/src/sgml/ref/show.sgml
+++ b/doc/src/sgml/ref/show.sgml
@@ -78,6 +78,15 @@ SHOW ALL
        </varlistentry>
 
        <varlistentry>
+        <term><literal>IN_HOT_STANDBY</literal></term>
+        <listitem>
+         <para>
+          True if the server is in Hot Standby mode.
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
         <term><literal>LC_COLLATE</literal></term>
         <listitem>
          <para>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index cdb3a8ac1d..acca53b12f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -7654,8 +7654,10 @@ StartupXLOG(void)
 	 * Shutdown the recovery environment. This must occur after
 	 * RecoverPreparedTransactions(), see notes for lock_twophase_recover()
 	 */
-	if (standbyState != STANDBY_DISABLED)
+	if (standbyState != STANDBY_DISABLED) {
 		ShutdownRecoveryTransactionEnvironment();
+		SendHotStandbyExitSignal();
+	}
 
 	/* Shut down xlogreader */
 	if (readFile >= 0)
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index e32d7a1d4e..8bc365489a 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -355,6 +355,8 @@ static List *upperPendingNotifies = NIL;		/* list of upper-xact lists */
  */
 volatile sig_atomic_t notifyInterruptPending = false;
 
+volatile sig_atomic_t hotStandbyExitInterruptPending = false;
+
 /* True if we've registered an on_shmem_exit cleanup */
 static bool unlistenExitRegistered = false;
 
@@ -1734,6 +1736,52 @@ ProcessNotifyInterrupt(void)
 
 
 /*
+ * HandleHotStandbyExitInterrupt
+ *
+ *		Signal handler portion of interrupt handling. Let the backend know
+ *		that the server has exited the recovery mode.
+ */
+void
+HandleHotStandbyExitInterrupt(void)
+{
+	/*
+	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
+	 * you do here.
+	 */
+
+	/* signal that work needs to be done */
+	hotStandbyExitInterruptPending = true;
+
+	/* make sure the event is processed in due course */
+	SetLatch(MyLatch);
+}
+
+
+/*
+ * ProcessHotStandbyExitInterrupt
+ *
+ *		This is called just after waiting for a frontend command.  If a
+ *		interrupt arrives (via HandleHotStandbyExitInterrupt()) while reading,
+ *		the read will be interrupted via the process's latch, and this routine
+ *		will get called.
+ */
+void
+ProcessHotStandbyExitInterrupt(void)
+{
+	hotStandbyExitInterruptPending = false;
+
+	SetConfigOption("in_hot_standby", "off",
+					PGC_INTERNAL, PGC_S_OVERRIDE);
+
+	/*
+	 * Flush output buffer so that clients receive the ParameterStatus
+	 * message as soon as possible.
+	 */
+	pq_flush();
+}
+
+
+/*
  * Read all pending notifications from the queue, and deliver appropriate
  * ones to my frontend.  Stop when we reach queue head or an uncommitted
  * notification.
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 0f8f435faf..b76ae35f87 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2937,6 +2937,36 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
 }
 
 /*
+ * SendSignalToAllBackends --- send a signal to all backends.
+ */
+void
+SendSignalToAllBackends(ProcSignalReason reason)
+{
+	ProcArrayStruct *arrayP = procArray;
+	int			index;
+	pid_t		pid = 0;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		VirtualTransactionId procvxid;
+
+		GET_VXID_FROM_PGPROC(procvxid, *proc);
+
+		pid = proc->pid;
+		if (pid != 0)
+		{
+			(void) SendProcSignal(pid, reason, procvxid.backendId);
+		}
+	}
+
+	LWLockRelease(ProcArrayLock);
+}
+
+/*
  * ProcArraySetReplicationSlotXmin
  *
  * Install limits to future computations of the xmin horizon to prevent vacuum
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 4a21d5512d..3918330ee3 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -288,6 +288,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
 		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
 
+	if (CheckProcSignal(PROCSIG_HOTSTANDBY_EXIT))
+		HandleHotStandbyExitInterrupt();
+
 	SetLatch(MyLatch);
 
 	latch_sigusr1_handler();
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 6259070722..f5155dd80c 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -111,6 +111,15 @@ ShutdownRecoveryTransactionEnvironment(void)
 	VirtualXactLockTableCleanup();
 }
 
+/*
+ * SendHotStandbyExitSignal
+ *		Signal backends that the server has exited Hot Standby.
+ */
+void
+SendHotStandbyExitSignal(void)
+{
+	SendSignalToAllBackends(PROCSIG_HOTSTANDBY_EXIT);
+}
 
 /*
  * -----------------------------------------------------
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b07d6c6cb9..4a2a4abb6f 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -532,9 +532,13 @@ ProcessClientReadInterrupt(bool blocked)
 		if (catchupInterruptPending)
 			ProcessCatchupInterrupt();
 
-		/* Process sinval catchup interrupts that happened while reading */
+		/* Process NOTIFY interrupts that happened while reading */
 		if (notifyInterruptPending)
 			ProcessNotifyInterrupt();
+
+		/* Process recovery exit interrupts that happened while reading */
+		if (hotStandbyExitInterruptPending)
+			ProcessHotStandbyExitInterrupt();
 	}
 	else if (ProcDiePending && blocked)
 	{
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 9f938f2d27..d0041de00b 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1013,6 +1013,12 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 	 * selected the active user and gotten the right GUC settings.
 	 */
 
+	/* set in_hot_standby */
+	if (HotStandbyActive()) {
+		SetConfigOption("in_hot_standby", "on",
+						PGC_INTERNAL, PGC_S_OVERRIDE);
+	}
+
 	/* set default namespace search path */
 	InitializeSearchPath();
 
diff --git a/src/backend/utils/misc/check_guc b/src/backend/utils/misc/check_guc
index d228bbed68..1795471fd4 100755
--- a/src/backend/utils/misc/check_guc
+++ b/src/backend/utils/misc/check_guc
@@ -17,11 +17,11 @@
 ## postgresql.conf.sample), it should be listed here so that it
 ## can be ignored
 INTENTIONALLY_NOT_INCLUDED="debug_deadlocks \
-is_superuser lc_collate lc_ctype lc_messages lc_monetary lc_numeric lc_time \
-pre_auth_delay role seed server_encoding server_version server_version_int \
-session_authorization trace_lock_oidmin trace_lock_table trace_locks trace_lwlocks \
-trace_notify trace_userlocks transaction_isolation transaction_read_only \
-zero_damaged_pages"
+is_superuser in_hot_standby lc_collate lc_ctype lc_messages lc_monetary \
+lc_numeric lc_time pre_auth_delay role seed server_encoding server_version \
+server_version_int session_authorization trace_lock_oidmin trace_lock_table \
+trace_locks trace_lwlocks trace_notify trace_userlocks transaction_isolation \
+transaction_read_only zero_damaged_pages"
 
 ### What options are listed in postgresql.conf.sample, but don't appear
 ### in guc.c?
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4feb26aa7a..b34553df81 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -496,6 +496,7 @@ int			huge_pages;
  */
 static char *syslog_ident_str;
 static bool session_auth_is_superuser;
+static bool server_in_hot_standby;
 static double phony_random_seed;
 static char *client_encoding_string;
 static char *datestyle_string;
@@ -934,6 +935,20 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
+		/*
+		 * Not for general use --- used to indicate whether
+		 * the server is in hot standby.
+		 */
+		{"in_hot_standby", PGC_INTERNAL, UNGROUPED,
+			gettext_noop("Shows whether the server is in hot standby mode."),
+			NULL,
+			GUC_REPORT | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+		},
+		&server_in_hot_standby,
+		false,
+		NULL, NULL, NULL
+	},
+	{
 		{"bonjour", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
 			gettext_noop("Enables advertising the server via Bonjour."),
 			NULL
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index b7842d1a0f..d21f8d59ae 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -24,6 +24,7 @@
 
 extern bool Trace_notify;
 extern volatile sig_atomic_t notifyInterruptPending;
+extern volatile sig_atomic_t hotStandbyExitInterruptPending;
 
 extern Size AsyncShmemSize(void);
 extern void AsyncShmemInit(void);
@@ -54,4 +55,10 @@ extern void HandleNotifyInterrupt(void);
 /* process interrupts */
 extern void ProcessNotifyInterrupt(void);
 
+/* signal handler for inbound notifies (PROCSIG_HOTSTANDBY_EXIT) */
+extern void HandleHotStandbyExitInterrupt(void);
+
+/* process recovery exit event */
+extern void ProcessHotStandbyExitInterrupt(void);
+
 #endif   /* ASYNC_H */
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index 9d5a13eb3b..a35759e141 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -79,6 +79,8 @@ extern int	CountUserBackends(Oid roleid);
 extern bool CountOtherDBBackends(Oid databaseId,
 					 int *nbackends, int *nprepared);
 
+extern void SendSignalToAllBackends(ProcSignalReason reason);
+
 extern void XidCacheRemoveRunningXids(TransactionId xid,
 						  int nxids, const TransactionId *xids,
 						  TransactionId latestXid);
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index d068dde5d7..557318a4c1 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -41,6 +41,8 @@ typedef enum
 	PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
 	PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
 
+	PROCSIG_HOTSTANDBY_EXIT,	/* postmaster has exited hot standby */
+
 	NUM_PROCSIGNALS				/* Must be last! */
 } ProcSignalReason;
 
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 3ecc446083..dabd8a3839 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -26,6 +26,7 @@ extern int	max_standby_streaming_delay;
 
 extern void InitRecoveryTransactionEnvironment(void);
 extern void ShutdownRecoveryTransactionEnvironment(void);
+extern void SendHotStandbyExitSignal(void);
 
 extern void ResolveRecoveryConflictWithSnapshot(TransactionId latestRemovedXid,
 									RelFileNode node);
-- 
2.11.1

