From 0cc6c9dc8b942222d0bf09d7b5cbd8f85c94cd30 Mon Sep 17 00:00:00 2001
From: Amul Sul <amul.sul@enterprisedb.com>
Date: Fri, 19 Jun 2020 06:29:36 -0400
Subject: [PATCH v4 3/5] Implement ALTER SYSTEM READ ONLY using global barrier.

Implementation:

 1. When a user tried to change server state to WAL-Prohibited using
	ALTER SYSTEM READ ONLY command; AlterSystemSetWALProhibitState()
	raises request to checkpointer by marking current state to inprogress in
	shared memory.  Checkpointer, noticing that the current state is has
	WALPROHIBIT_TRANSITION_IN_PROGRESS flag set, does the barrier request, and
	then acknowledges back to the backend who requested the state change once
	the transition has been completed.  Final state will be updated in control
	file to make it persistent across the system restarts.

 2. When a backend receives the WAL-Prohibited barrier, at that moment if
    it is already in a transaction and the transaction already assigned XID,
    then the backend will be killed by throwing FATAL(XXX: need more discussion
    on this)

 3. Otherwise, if that backend running transaction which yet to get XID
    assigned we don't need to do anything special, simply call
    ResetLocalXLogInsertAllowed() so that any future WAL insert in will check
    XLogInsertAllowed() first which set ready only state appropriately.

 4. A new transaction (from existing or new backend) starts as a read-only
    transaction.

 5. Autovacuum launcher as well as checkpointer will don't do anything in
 	WAL-Prohibited server state until someone wakes us up.  E.g. a backend
	might later on request us to put the system back to read-write.

 6. At shutdown in WAL-Prohibited mode, we'll skip shutdown checkpoint
    and xlog rotation. Starting up again will perform crash recovery(XXX:
    need some discussion on this as well)

 7. ALTER SYSTEM READ ONLY/WRITE is restricted on standby server.

 8. Only super user can toggle WAL-Prohibit state.

 9. Add system_is_read_only GUC show the system state -- will true when system
    is wal prohibited or in recovery.
---
 src/backend/access/transam/Makefile      |   1 +
 src/backend/access/transam/walprohibit.c |  97 +++++++++++++++
 src/backend/access/transam/xact.c        |  49 +++++---
 src/backend/access/transam/xlog.c        | 150 +++++++++++++++++++++--
 src/backend/postmaster/autovacuum.c      |   4 +
 src/backend/postmaster/bgwriter.c        |   2 +-
 src/backend/postmaster/checkpointer.c    | 117 ++++++++++++++++++
 src/backend/postmaster/pgstat.c          |   3 +
 src/backend/storage/ipc/procsignal.c     |  26 +---
 src/backend/tcop/utility.c               |  14 +--
 src/backend/utils/misc/guc.c             |  26 ++++
 src/bin/pg_controldata/pg_controldata.c  |   2 +
 src/include/access/walprohibit.h         |  32 +++++
 src/include/access/xlog.h                |   3 +
 src/include/catalog/pg_control.h         |   3 +
 src/include/pgstat.h                     |   1 +
 src/include/postmaster/bgwriter.h        |   2 +
 src/include/storage/procsignal.h         |   7 +-
 18 files changed, 466 insertions(+), 73 deletions(-)
 create mode 100644 src/backend/access/transam/walprohibit.c
 create mode 100644 src/include/access/walprohibit.h

diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 595e02de722..b5322a69954 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -26,6 +26,7 @@ OBJS = \
 	twophase.o \
 	twophase_rmgr.o \
 	varsup.o \
+	walprohibit.o \
 	xact.o \
 	xlog.o \
 	xlogarchive.o \
diff --git a/src/backend/access/transam/walprohibit.c b/src/backend/access/transam/walprohibit.c
new file mode 100644
index 00000000000..a3f1a750744
--- /dev/null
+++ b/src/backend/access/transam/walprohibit.c
@@ -0,0 +1,97 @@
+/*-------------------------------------------------------------------------
+ *
+ * walprohibit.c
+ * 		PostgreSQL write-ahead log prohibit states
+ *
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/backend/access/transam/walprohibit.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/walprohibit.h"
+#include "postmaster/bgwriter.h"
+#include "storage/procsignal.h"
+
+/*
+ * ProcessBarrierWALProhibit()
+ *
+ * Handle WAL prohibit state change request.
+ */
+bool
+ProcessBarrierWALProhibit(void)
+{
+	/*
+	 * Kill off any transactions that have an XID *before* allowing the system
+	 * to go WAL prohibit state.
+	 */
+	if (FullTransactionIdIsValid(GetTopFullTransactionIdIfAny()))
+	{
+		Assert(GetWALProhibitState() & WALPROHIBIT_STATE_READ_ONLY);
+
+		/*
+		 * XXX: Kill off the whole session by throwing FATAL instead of killing
+		 * transaction by throwing ERROR due to following reasons that need be
+		 * thought:
+		 *
+		 * 1. Due to some presents challenges with the wire protocol, we could
+		 * not simply kill of idle transaction.
+		 *
+		 * 2. If we are here in subtransaction then the ERROR will kill the
+		 * current subtransaction only. In the case of invalidations, that
+		 * might be good enough, but for XID assignment it's not, because
+		 * assigning an XID to a subtransaction also causes higher
+		 * sub-transaction levels and the parent transaction to get XIDs.
+		 */
+		ereport(FATAL,
+				(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+				 errmsg("system is now read only"),
+				 errhint("Cannot continue a transaction if it has performed writes while system is read only.")));
+	}
+
+	/* Return to "check" state */
+	ResetLocalXLogInsertAllowed();
+
+	return true;
+}
+
+/*
+ * AlterSystemSetWALProhibitState
+ *
+ * Execute ALTER SYSTEM READ { ONLY | WRITE } statement.
+ */
+void
+AlterSystemSetWALProhibitState(AlterSystemWALProhibitState *stmt)
+{
+	uint32		state;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("must be superuser to execute ALTER SYSTEM command")));
+
+	/* Alter WAL prohibit state not allowed during recovery */
+	PreventCommandDuringRecovery("ALTER SYSTEM");
+
+	/* Requested state */
+	state = stmt->WALProhibited ?
+		WALPROHIBIT_STATE_READ_ONLY : WALPROHIBIT_STATE_READ_WRITE;
+
+	/*
+	 * Since we yet to convey this WAL prohibit state to all backend mark it
+	 * in-progress.
+	 */
+	state |= WALPROHIBIT_TRANSITION_IN_PROGRESS;
+
+	if (!SetWALProhibitState(state))
+		return; /* server is already in the desired state */
+
+	/*
+	 * Signal the checkpointer to do the actual state transition, and wait for
+	 * the state change to occur.
+	 */
+	WALProhibitRequest();
+}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d4f7c29847f..8ebc4242c25 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1949,23 +1949,28 @@ StartTransaction(void)
 	Assert(s->prevSecContext == 0);
 
 	/*
-	 * Make sure we've reset xact state variables
+	 * Reset xact state variables.
 	 *
-	 * If recovery is still in progress, mark this transaction as read-only.
-	 * We have lower level defences in XLogInsert and elsewhere to stop us
-	 * from modifying data during recovery, but this gives the normal
-	 * indication to the user that the transaction is read-only.
-	 */
-	if (RecoveryInProgress())
-	{
-		s->startedInRecovery = true;
-		XactReadOnly = true;
-	}
-	else
-	{
-		s->startedInRecovery = false;
-		XactReadOnly = DefaultXactReadOnly;
-	}
+	 * If it is not currently possible to insert write-ahead log records,
+	 * either because we are still in recovery or because ALTER SYSTEM READ
+	 * ONLY has been executed, force this to be a read-only transaction.
+	 * We have lower level defences in XLogBeginInsert() and elsewhere to stop
+	 * us from modifying data during recovery when !XLogInsertAllowed(), but
+	 * this gives the normal indication to the user that the transaction is
+	 * read-only.
+	 *
+	 * On the other hand, we only need to set the startedInRecovery flag when
+	 * the transaction started during recovery, and not when WAL is otherwise
+	 * prohibited. This information is used by RelationGetIndexScan() to
+	 * decide whether to permit (1) relying on existing killed-tuple markings
+	 * and (2) further killing of index tuples. Even when WAL is prohibited
+	 * on the master, it's still the master, so the former is OK; and since
+	 * killing index tuples doesn't generate WAL, the latter is also OK.
+	 * See comments in RelationGetIndexScan() and MarkBufferDirtyHint().
+	 */
+	XactReadOnly = DefaultXactReadOnly || !XLogInsertAllowed();
+	s->startedInRecovery = RecoveryInProgress();
+
 	XactDeferrable = DefaultXactDeferrable;
 	XactIsoLevel = DefaultXactIsoLevel;
 	forceSyncCommit = false;
@@ -4887,9 +4892,11 @@ CommitSubTransaction(void)
 	/*
 	 * We need to restore the upper transaction's read-only state, in case the
 	 * upper is read-write while the child is read-only; GUC will incorrectly
-	 * think it should leave the child state in place.
+	 * think it should leave the child state in place.  Note that the upper
+	 * transaction will be a force to ready-only irrespective of its previous
+	 * status if the server state is WAL prohibited.
 	 */
-	XactReadOnly = s->prevXactReadOnly;
+	XactReadOnly = s->prevXactReadOnly || !XLogInsertAllowed();
 
 	CurrentResourceOwner = s->parent->curTransactionOwner;
 	CurTransactionResourceOwner = s->parent->curTransactionOwner;
@@ -5045,9 +5052,11 @@ AbortSubTransaction(void)
 	/*
 	 * Restore the upper transaction's read-only state, too.  This should be
 	 * redundant with GUC's cleanup but we may as well do it for consistency
-	 * with the commit case.
+	 * with the commit case.  Note that the upper transaction will be a force
+	 * to ready-only irrespective of its previous status if the server state is
+	 * WAL prohibited.
 	 */
-	XactReadOnly = s->prevXactReadOnly;
+	XactReadOnly = s->prevXactReadOnly || !XLogInsertAllowed();
 
 	RESUME_INTERRUPTS();
 }
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 184c6672f3b..6d33ea226b1 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -31,6 +31,7 @@
 #include "access/timeline.h"
 #include "access/transam.h"
 #include "access/twophase.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "access/xlogarchive.h"
@@ -245,9 +246,10 @@ static bool LocalPromoteIsTriggered = false;
  *		0: unconditionally not allowed to insert XLOG
  *		-1: must check RecoveryInProgress(); disallow until it is false
  * Most processes start with -1 and transition to 1 after seeing that recovery
- * is not in progress.  But we can also force the value for special cases.
- * The coding in XLogInsertAllowed() depends on the first two of these states
- * being numerically the same as bool true and false.
+ * is not in progress or the server state is not a WAL prohibited state.  But
+ * we can also force the value for special cases.  The coding in
+ * XLogInsertAllowed() depends on the first two of these states being
+ * numerically the same as bool true and false.
  */
 static int	LocalXLogInsertAllowed = -1;
 
@@ -660,6 +662,12 @@ typedef struct XLogCtlData
 	 */
 	RecoveryState SharedRecoveryState;
 
+	/*
+	 * SharedWALProhibitState indicates current WAL prohibit state.
+	 * Protected by info_lck.
+	 */
+	uint32		SharedWALProhibitState;
+
 	/*
 	 * SharedHotStandbyActive indicates if we allow hot standby queries to be
 	 * run.  Protected by info_lck.
@@ -970,6 +978,7 @@ static void WALInsertLockAcquire(void);
 static void WALInsertLockAcquireExclusive(void);
 static void WALInsertLockRelease(void);
 static void WALInsertLockUpdateInsertingAt(XLogRecPtr insertingAt);
+static inline bool IsWALProhibited(void);
 
 /*
  * Insert an XLOG record represented by an already-constructed chain of data
@@ -7706,6 +7715,15 @@ StartupXLOG(void)
 	XLogCtl->LogwrtRqst.Write = EndOfLog;
 	XLogCtl->LogwrtRqst.Flush = EndOfLog;
 
+	/*
+	 * Before enabling WAL insertion update WAL prohibit state in shared memory
+	 * that will decide the further WAL insert should be allowed or not.
+	 */
+	SpinLockAcquire(&XLogCtl->info_lck);
+	XLogCtl->SharedWALProhibitState = ControlFile->wal_prohibited ?
+		WALPROHIBIT_STATE_READ_ONLY : WALPROHIBIT_STATE_READ_WRITE;
+	SpinLockRelease(&XLogCtl->info_lck);
+
 	/*
 	 * Update full_page_writes in shared memory and write an XLOG_FPW_CHANGE
 	 * record before resource manager writes cleanup WAL records or checkpoint
@@ -7716,7 +7734,15 @@ StartupXLOG(void)
 	UpdateFullPageWrites();
 	LocalXLogInsertAllowed = -1;
 
-	if (InRecovery)
+	/*
+	 * Skip end-of-recovery checkpoint if the system is in WAL prohibited state.
+	 */
+	if (ControlFile->wal_prohibited && InRecovery)
+	{
+		ereport(LOG,
+				(errmsg("skipping startup checkpoint because the system is read only")));
+	}
+	else if (InRecovery)
 	{
 		/*
 		 * Perform a checkpoint to update all our recovery activity to disk.
@@ -7962,6 +7988,83 @@ StartupXLOG(void)
 		RequestCheckpoint(CHECKPOINT_FORCE);
 }
 
+/* Atomically return the current server WAL prohibited state */
+uint32
+GetWALProhibitState(void)
+{
+	uint32		state;
+
+	SpinLockAcquire(&XLogCtl->info_lck);
+	state = XLogCtl->SharedWALProhibitState;
+	SpinLockRelease(&XLogCtl->info_lck);
+
+	return state;
+}
+
+/*
+ * SetWALProhibitState: Change current wal prohibit state to the input state.
+ *
+ * If the server is already completely moved to the requested WAL prohibit
+ * state, or if the desired state is same as the current state, return false,
+ * indicating that the server state did not change. Else return true.
+ */
+bool
+SetWALProhibitState(uint32 new_state)
+{
+	uint32		cur_state;
+
+	cur_state = GetWALProhibitState();
+
+	/* Server is already in requested state */
+	if (new_state == cur_state ||
+		new_state == (cur_state | WALPROHIBIT_TRANSITION_IN_PROGRESS))
+		return false;
+
+	/* Prevent concurrent contrary in progress transition state setting */
+	if ((new_state & WALPROHIBIT_TRANSITION_IN_PROGRESS) &&
+		(cur_state & WALPROHIBIT_TRANSITION_IN_PROGRESS))
+	{
+		if (cur_state & WALPROHIBIT_STATE_READ_ONLY)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("system state transition to read only is already in progress"),
+					 errhint("Try after sometime again.")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("system state transition to read write is already in progress"),
+					 errhint("Try after sometime again.")));
+
+	}
+
+	/* Update new state in share memory */
+	SpinLockAcquire(&XLogCtl->info_lck);
+	XLogCtl->SharedWALProhibitState = new_state;
+	SpinLockRelease(&XLogCtl->info_lck);
+
+	/* Update control file if it is the final state */
+	if (!(new_state & WALPROHIBIT_TRANSITION_IN_PROGRESS))
+	{
+		bool	wal_prohibited = (new_state & WALPROHIBIT_STATE_READ_ONLY) != 0;
+
+		LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
+		ControlFile->wal_prohibited = wal_prohibited;
+		UpdateControlFile();
+		LWLockRelease(ControlFileLock);
+	}
+
+	return true;
+}
+
+/*
+ * Is the system still in WAL prohibited state?
+ */
+static inline bool
+IsWALProhibited(void)
+{
+	return (GetWALProhibitState() & WALPROHIBIT_STATE_READ_ONLY) != 0;
+}
+
 /*
  * Checks if recovery has reached a consistent state. When consistency is
  * reached and we have a valid starting standby snapshot, tell postmaster
@@ -8177,9 +8280,9 @@ HotStandbyActiveInReplay(void)
 /*
  * Is this process allowed to insert new WAL records?
  *
- * Ordinarily this is essentially equivalent to !RecoveryInProgress().
- * But we also have provisions for forcing the result "true" or "false"
- * within specific processes regardless of the global state.
+ * Ordinarily this is essentially equivalent to !RecoveryInProgress() and
+ * !IsWALProhibited().  But we also have provisions for forcing the result
+ * "true" or "false" within specific processes regardless of the global state.
  */
 bool
 XLogInsertAllowed(void)
@@ -8193,14 +8296,25 @@ XLogInsertAllowed(void)
 		return (bool) LocalXLogInsertAllowed;
 
 	/*
-	 * Else, must check to see if we're still in recovery.
+	 * Else, must check to see if we're still in recovery
 	 */
 	if (RecoveryInProgress())
 		return false;
 
+	/* Or, in WAL prohibited state */
+	if (IsWALProhibited())
+	{
+		/*
+		 * Set it to "unconditionally false" to avoid checking until it gets
+		 * reset.
+		 */
+		LocalXLogInsertAllowed = 0;
+		return false;
+	}
+
 	/*
-	 * On exit from recovery, reset to "unconditionally true", since there is
-	 * no need to keep checking.
+	 * On exit from recovery or WAL prohibited state, reset to "unconditionally
+	 * true", since there is no need to keep checking.
 	 */
 	LocalXLogInsertAllowed = 1;
 	return true;
@@ -8216,12 +8330,19 @@ static void
 LocalSetXLogInsertAllowed(void)
 {
 	Assert(LocalXLogInsertAllowed == -1);
+
 	LocalXLogInsertAllowed = 1;
 
 	/* Initialize as RecoveryInProgress() would do when switching state */
 	InitXLOGAccess();
 }
 
+void
+ResetLocalXLogInsertAllowed(void)
+{
+	LocalXLogInsertAllowed = -1;
+}
+
 /*
  * Subroutine to try to fetch and validate a prior checkpoint record.
  *
@@ -8513,7 +8634,10 @@ ShutdownXLOG(int code, Datum arg)
 
 	if (RecoveryInProgress())
 		CreateRestartPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
-	else
+	/*
+	 * Can't perform checkpoint or xlog rotation without writing WAL.
+	 */
+	else if (XLogInsertAllowed())
 	{
 		/*
 		 * If archiving is enabled, rotate the last XLOG file so that all the
@@ -8526,6 +8650,10 @@ ShutdownXLOG(int code, Datum arg)
 
 		CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
 	}
+	else
+		ereport(LOG,
+			   (errmsg("skipping shutdown checkpoint because the system is read only")));
+
 	ShutdownCLOG();
 	ShutdownCommitTs();
 	ShutdownSUBTRANS();
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60e..f83f86994db 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -652,6 +652,10 @@ AutoVacLauncherMain(int argc, char *argv[])
 
 		HandleAutoVacLauncherInterrupts();
 
+		/* If the server is read only just go back to sleep. */
+		if (!XLogInsertAllowed())
+			continue;
+
 		/*
 		 * a worker finished, or postmaster signaled failure to start a worker
 		 */
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 069e27e427f..6c6ff7dc3af 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -268,7 +268,7 @@ BackgroundWriterMain(void)
 		 * Checkpointer, when active, is barely ever in its mainloop and thus
 		 * makes it hard to log regularly.
 		 */
-		if (XLogStandbyInfoActive() && !RecoveryInProgress())
+		if (XLogStandbyInfoActive() && XLogInsertAllowed())
 		{
 			TimestampTz timeout = 0;
 			TimestampTz now = GetCurrentTimestamp();
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 624a3238b80..3e8aa9a0ec3 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -39,6 +39,7 @@
 #include <sys/time.h>
 #include <time.h>
 
+#include "access/walprohibit.h"
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
 #include "libpq/pqsignal.h"
@@ -127,6 +128,9 @@ typedef struct
 	ConditionVariable start_cv; /* signaled when ckpt_started advances */
 	ConditionVariable done_cv;	/* signaled when ckpt_done advances */
 
+	ConditionVariable walprohibit_cv; /* signaled when requested wal
+										 prohibit state changes */
+
 	uint32		num_backend_writes; /* counts user backend buffer writes */
 	uint32		num_backend_fsync;	/* counts user backend fsync calls */
 
@@ -168,6 +172,7 @@ static bool IsCheckpointOnSchedule(double progress);
 static bool ImmediateCheckpointRequested(void);
 static bool CompactCheckpointerRequestQueue(void);
 static void UpdateSharedMemoryConfig(void);
+static void performWALProhibitStateChange(uint32 wal_state);
 
 /* Signal handlers */
 static void ReqCheckpointHandler(SIGNAL_ARGS);
@@ -332,6 +337,7 @@ CheckpointerMain(void)
 		pg_time_t	now;
 		int			elapsed_secs;
 		int			cur_timeout;
+		uint32		wal_state;
 
 		/* Clear any already-pending wakeups */
 		ResetLatch(MyLatch);
@@ -342,6 +348,28 @@ CheckpointerMain(void)
 		AbsorbSyncRequests();
 		HandleCheckpointerInterrupts();
 
+		wal_state = GetWALProhibitState();
+
+		if (wal_state & WALPROHIBIT_TRANSITION_IN_PROGRESS)
+		{
+			/* Complete WAL prohibit state change request */
+			performWALProhibitStateChange(wal_state);
+			continue;
+		}
+		else if (wal_state & WALPROHIBIT_STATE_READ_ONLY)
+		{
+			/*
+			 * Don't do anything until someone wakes us up.  For example a
+			 * backend might later on request us to put the system back to
+			 * read-write wal prohibit sate.
+			 */
+			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_CHECKPOINTER_MAIN);
+			continue;
+		}
+
+		Assert(wal_state == WALPROHIBIT_STATE_READ_WRITE);
+
 		/*
 		 * Detect a pending checkpoint request by checking whether the flags
 		 * word in shared memory is nonzero.  We shouldn't need to acquire the
@@ -879,6 +907,7 @@ CheckpointerShmemInit(void)
 		CheckpointerShmem->max_requests = NBuffers;
 		ConditionVariableInit(&CheckpointerShmem->start_cv);
 		ConditionVariableInit(&CheckpointerShmem->done_cv);
+		ConditionVariableInit(&CheckpointerShmem->walprohibit_cv);
 	}
 }
 
@@ -1109,6 +1138,94 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 	return true;
 }
 
+/*
+ * WALProhibitedRequest: Request checkpointer to make the WALProhibitState to
+ * read-only.
+ */
+void
+WALProhibitRequest(void)
+{
+	/* Must not be called from checkpointer */
+	Assert(!AmCheckpointerProcess());
+	Assert(GetWALProhibitState() & WALPROHIBIT_TRANSITION_IN_PROGRESS);
+
+	/*
+	 * If in a standalone backend, just do it ourselves.
+	 */
+	if (!IsPostmasterEnvironment)
+	{
+		performWALProhibitStateChange(GetWALProhibitState());
+		return;
+	}
+
+	if (CheckpointerShmem->checkpointer_pid == 0)
+		elog(ERROR, "checkpointer is not running");
+
+	if (kill(CheckpointerShmem->checkpointer_pid, SIGINT) != 0)
+		elog(ERROR, "could not signal checkpointer: %m");
+
+	/* Wait for the state to change to read-only */
+	ConditionVariablePrepareToSleep(&CheckpointerShmem->walprohibit_cv);
+	for (;;)
+	{
+		/*  We'll be done once in-progress flag bit is cleared */
+		if (!(GetWALProhibitState() & WALPROHIBIT_TRANSITION_IN_PROGRESS))
+			break;
+
+		elog(DEBUG1, "WALProhibitRequest: Waiting for checkpointer");
+		ConditionVariableSleep(&CheckpointerShmem->walprohibit_cv,
+							   WAIT_EVENT_SYSTEM_WALPROHIBIT_STATE_CHANGE);
+	}
+	ConditionVariableCancelSleep();
+	elog(DEBUG1, "Done WALProhibitRequest");
+}
+
+/*
+ * performWALProhibitStateChange: checkpointer will call this to complete
+ * the requested WAL prohibit state transition.
+ */
+static void
+performWALProhibitStateChange(uint32 wal_state)
+{
+	uint64		barrierGeneration;
+
+	/*
+	 * Must be called from checkpointer. Otherwise, it must be single-user
+	 * backend.
+	 */
+	Assert(AmCheckpointerProcess() || !IsPostmasterEnvironment);
+	Assert(wal_state & WALPROHIBIT_TRANSITION_IN_PROGRESS);
+
+	/*
+	 * WAL prohibit state change is initiated. We need to complete the state
+	 * transition by setting requested WAL prohibit state in all backends.
+	 */
+	elog(DEBUG1, "waiting for backends to adopt requested WAL prohibit state");
+
+	/* Emit global barrier */
+	barrierGeneration = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_WALPROHIBIT);
+	WaitForProcSignalBarrier(barrierGeneration);
+
+	/* And flush all writes. */
+	XLogFlush(GetXLogWriteRecPtr());
+
+	/* Set final state by clearing in-progress flag bit */
+	if (SetWALProhibitState(wal_state & ~(WALPROHIBIT_TRANSITION_IN_PROGRESS)))
+	{
+		if ((wal_state & WALPROHIBIT_STATE_READ_ONLY) != 0)
+			ereport(LOG, (errmsg("system is now read only")));
+		else
+		{
+			/* Request checkpoint */
+			RequestCheckpoint(CHECKPOINT_IMMEDIATE);
+			ereport(LOG, (errmsg("system is now read write")));
+		}
+	}
+
+	/* Wake up the backend who requested the state change */
+	ConditionVariableBroadcast(&CheckpointerShmem->walprohibit_cv);
+}
+
 /*
  * CompactCheckpointerRequestQueue
  *		Remove duplicates from the request queue to avoid backend fsyncs.
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 88992c2da2c..0eb40a86b52 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -4057,6 +4057,9 @@ pgstat_get_wait_io(WaitEventIO w)
 		case WAIT_EVENT_REPLICATION_SLOT_WRITE:
 			event_name = "ReplicationSlotWrite";
 			break;
+		case WAIT_EVENT_SYSTEM_WALPROHIBIT_STATE_CHANGE:
+			event_name = "SystemWALProhibitStateChange";
+			break;
 		case WAIT_EVENT_SLRU_FLUSH_SYNC:
 			event_name = "SLRUFlushSync";
 			break;
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 13648887187..b973727a580 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -18,6 +18,7 @@
 #include <unistd.h>
 
 #include "access/parallel.h"
+#include "access/walprohibit.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -96,7 +97,6 @@ static volatile ProcSignalSlot *MyProcSignalSlot = NULL;
 
 static bool CheckProcSignal(ProcSignalReason reason);
 static void CleanupProcSignalState(int status, Datum arg);
-static bool ProcessBarrierPlaceholder(void);
 
 /*
  * ProcSignalShmemSize
@@ -510,9 +510,9 @@ ProcessProcSignalBarrier(void)
 			 * unconditionally, but it's more efficient to call only the ones
 			 * that might need us to do something based on the flags.
 			 */
-			if (BARRIER_SHOULD_CHECK(flags, PROCSIGNAL_BARRIER_PLACEHOLDER)
-				&& ProcessBarrierPlaceholder())
-				BARRIER_CLEAR_BIT(flags, PROCSIGNAL_BARRIER_PLACEHOLDER);
+			if (BARRIER_SHOULD_CHECK(flags, PROCSIGNAL_BARRIER_WALPROHIBIT)
+				&& ProcessBarrierWALProhibit())
+				BARRIER_CLEAR_BIT(flags, PROCSIGNAL_BARRIER_WALPROHIBIT);
 		}
 		PG_CATCH();
 		{
@@ -554,24 +554,6 @@ ProcessProcSignalBarrier(void)
 	pg_atomic_write_u64(&MyProcSignalSlot->pss_barrierGeneration, shared_gen);
 }
 
-static bool
-ProcessBarrierPlaceholder(void)
-{
-	/*
-	 * XXX. This is just a placeholder until the first real user of this
-	 * machinery gets committed. Rename PROCSIGNAL_BARRIER_PLACEHOLDER to
-	 * PROCSIGNAL_BARRIER_SOMETHING_ELSE where SOMETHING_ELSE is something
-	 * appropriately descriptive. Get rid of this function and instead have
-	 * ProcessBarrierSomethingElse. Most likely, that function should live in
-	 * the file pertaining to that subsystem, rather than here.
-	 *
-	 * The return value should be 'true' if the barrier was successfully
-	 * absorbed and 'false' if not. Note that returning 'false' can lead to
-	 * very frequent retries, so try hard to make that an uncommon case.
-	 */
-	return true;
-}
-
 /*
  * CheckProcSignal - check to see if a particular reason has been
  * signaled, and clear the signal flag.  Should be called after receiving
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 7af96c77082..d6411e4f3e9 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -19,6 +19,7 @@
 #include "access/htup_details.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -85,7 +86,6 @@ static void ProcessUtilitySlow(ParseState *pstate,
 							   DestReceiver *dest,
 							   QueryCompletion *qc);
 static void ExecDropStmt(DropStmt *stmt, bool isTopLevel);
-static void AlterSystemSetWALProhibitState(AlterSystemWALProhibitState *stmt);
 
 /*
  * CommandIsReadOnly: is an executable query read-only?
@@ -3644,15 +3644,3 @@ GetCommandLogLevel(Node *parsetree)
 
 	return lev;
 }
-
-/*
- * AlterSystemSetWALProhibitState
- *
- * Execute ALTER SYSTEM READ { ONLY | WRITE } statement.
- */
-static void
-AlterSystemSetWALProhibitState(AlterSystemWALProhibitState *stmt)
-{
-	/* some code */
-	elog(INFO, "AlterSystemSetWALProhibitState() called");
-}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index abfa95a2314..a2f796164a7 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -225,6 +225,7 @@ static bool check_recovery_target_lsn(char **newval, void **extra, GucSource sou
 static void assign_recovery_target_lsn(const char *newval, void *extra);
 static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
 static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
+static const char *show_system_is_read_only(void);
 
 /* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -615,6 +616,7 @@ static char *recovery_target_string;
 static char *recovery_target_xid_string;
 static char *recovery_target_name_string;
 static char *recovery_target_lsn_string;
+static bool system_is_read_only;
 
 
 /* should be static, but commands/variable.c needs to get at this */
@@ -2036,6 +2038,18 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		/* Not for general use */
+		{"system_is_read_only", PGC_INTERNAL, WAL,
+			gettext_noop("Shows whether the system is read only."),
+			NULL,
+			GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+		},
+		&system_is_read_only,
+		false,
+		NULL, NULL, show_system_is_read_only
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
@@ -12019,4 +12033,16 @@ check_default_with_oids(bool *newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * NB: The return string should be the same as the _ShowOption() for boolean
+ * type.
+ */
+ static const char *
+ show_system_is_read_only(void)
+{
+	if (!XLogInsertAllowed())
+		return "on";
+	return "off";
+}
+
 #include "guc-file.c"
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index e73639df744..9594df76946 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -290,6 +290,8 @@ main(int argc, char *argv[])
 		   (uint32) ControlFile->backupEndPoint);
 	printf(_("End-of-backup record required:        %s\n"),
 		   ControlFile->backupEndRequired ? _("yes") : _("no"));
+	printf(_("WAL write prohibited:                 %s\n"),
+		   ControlFile->wal_prohibited ? _("yes") : _("no"));
 	printf(_("wal_level setting:                    %s\n"),
 		   wal_level_str(ControlFile->wal_level));
 	printf(_("wal_log_hints setting:                %s\n"),
diff --git a/src/include/access/walprohibit.h b/src/include/access/walprohibit.h
new file mode 100644
index 00000000000..163fe0d2fce
--- /dev/null
+++ b/src/include/access/walprohibit.h
@@ -0,0 +1,32 @@
+/*
+ * walprohibit.h
+ *
+ * PostgreSQL write-ahead log prohibit states
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/access/walprohibit.h
+ */
+#ifndef WALPROHIBIT_H
+#define WALPROHIBIT_H
+
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "miscadmin.h"
+#include "nodes/parsenodes.h"
+
+extern bool ProcessBarrierWALProhibit(void);
+extern void AlterSystemSetWALProhibitState(AlterSystemWALProhibitState *stmt);
+
+/* WAL Prohibit States */
+#define	WALPROHIBIT_STATE_READ_WRITE		0x0000
+#define	WALPROHIBIT_STATE_READ_ONLY			0x0001
+
+/*
+ * The bit is used in state transition from one state to another.  When this
+ * bit is set then the state indicated by the 0th position bit is yet to
+ * confirmed.
+ */
+#define WALPROHIBIT_TRANSITION_IN_PROGRESS	0x0002
+
+#endif		/* WALPROHIBIT_H */
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 219a7299e1f..5f5c2146e7e 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -306,6 +306,7 @@ extern RecoveryState GetRecoveryState(void);
 extern bool HotStandbyActive(void);
 extern bool HotStandbyActiveInReplay(void);
 extern bool XLogInsertAllowed(void);
+extern void ResetLocalXLogInsertAllowed(void);
 extern void GetXLogReceiptTime(TimestampTz *rtime, bool *fromStream);
 extern XLogRecPtr GetXLogReplayRecPtr(TimeLineID *replayTLI);
 extern XLogRecPtr GetXLogInsertRecPtr(void);
@@ -325,6 +326,8 @@ extern void XLOGShmemInit(void);
 extern void BootStrapXLOG(void);
 extern void LocalProcessControlFile(bool reset);
 extern void StartupXLOG(void);
+extern uint32 GetWALProhibitState(void);
+extern bool SetWALProhibitState(uint32 new_state);
 extern void ShutdownXLOG(int code, Datum arg);
 extern void InitXLOGAccess(void);
 extern void CreateCheckPoint(int flags);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index de5670e5382..b32c7723275 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -182,6 +182,9 @@ typedef struct ControlFileData
 	int			max_locks_per_xact;
 	bool		track_commit_timestamp;
 
+	/* WAL prohibited determines if the WAL insert is allowed or not. */
+	bool		wal_prohibited;
+
 	/*
 	 * This data is used to check for hardware-architecture compatibility of
 	 * the database and the backend executable.  We need not check endianness
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 13872013823..780c59f3e48 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -955,6 +955,7 @@ typedef enum
 	WAIT_EVENT_REPLICATION_SLOT_RESTORE_SYNC,
 	WAIT_EVENT_REPLICATION_SLOT_SYNC,
 	WAIT_EVENT_REPLICATION_SLOT_WRITE,
+	WAIT_EVENT_SYSTEM_WALPROHIBIT_STATE_CHANGE,
 	WAIT_EVENT_SLRU_FLUSH_SYNC,
 	WAIT_EVENT_SLRU_READ,
 	WAIT_EVENT_SLRU_SYNC,
diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h
index 0a5708b32e6..e8271b49f6d 100644
--- a/src/include/postmaster/bgwriter.h
+++ b/src/include/postmaster/bgwriter.h
@@ -35,6 +35,8 @@ extern void CheckpointWriteDelay(int flags, double progress);
 
 extern bool ForwardSyncRequest(const FileTag *ftag, SyncRequestType type);
 
+extern void WALProhibitRequest(void);
+
 extern void AbsorbSyncRequests(void);
 
 extern Size CheckpointerShmemSize(void);
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 5cb39697f38..bae06202b4a 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -48,12 +48,7 @@ typedef enum
 
 typedef enum
 {
-	/*
-	 * XXX. PROCSIGNAL_BARRIER_PLACEHOLDER should be replaced when the first
-	 * real user of the ProcSignalBarrier mechanism is added. It's just here
-	 * for now because we can't have an empty enum.
-	 */
-	PROCSIGNAL_BARRIER_PLACEHOLDER = 0
+	PROCSIGNAL_BARRIER_WALPROHIBIT = 0
 } ProcSignalBarrierType;
 
 /*
-- 
2.22.0

