From 0b7426fc4708cc0e4ad333da3b35e473658bba28 Mon Sep 17 00:00:00 2001
From: Amul Sul <amul.sul@enterprisedb.com>
Date: Tue, 14 Jul 2020 02:10:55 -0400
Subject: [PATCH v3 5/6] Error or Assert before START_CRIT_SECTION for WAL
 write

Based on the following criteria added the Assert or the Error when system is
prohibited:

 - Added ERROR for the function which can be reachable without valid XID in case
   of VACUUM or CONCURRENT CREATE INDEX.  For that added common static inline
   function CheckWALPermitted().
 - Added ASSERT for the function which cannot be reached without valid XID;
   Assert to ensure XID validation. For that added AssertWALPermitted_HaveXID().

To enforce the rule to have aforesaid assert or error check before entering a
critical section for WAL write, a new walpermit_checked_state assert only flag
is added. If these check is missing then XLogBeginInsert() will have an
assertion if it is in critical section.

If we are not doing WAL insert inside the critical section then the above
checking is not necessary, we can rely on XLogBeginInsert() for that check &
report an error.
---
 src/backend/access/brin/brin.c            |  4 ++
 src/backend/access/brin/brin_pageops.c    | 16 ++++++++
 src/backend/access/brin/brin_revmap.c     |  8 ++++
 src/backend/access/gin/ginbtree.c         | 17 ++++++--
 src/backend/access/gin/gindatapage.c      | 14 ++++++-
 src/backend/access/gin/ginfast.c          |  8 ++++
 src/backend/access/gin/gininsert.c        |  4 ++
 src/backend/access/gin/ginutil.c          | 10 ++++-
 src/backend/access/gin/ginvacuum.c        |  9 +++++
 src/backend/access/gist/gist.c            | 16 ++++++++
 src/backend/access/gist/gistvacuum.c      |  9 +++++
 src/backend/access/hash/hash.c            | 13 ++++++
 src/backend/access/hash/hashinsert.c      |  8 ++++
 src/backend/access/hash/hashovfl.c        | 14 +++++++
 src/backend/access/hash/hashpage.c        | 13 ++++++
 src/backend/access/heap/heapam.c          | 32 +++++++++++++++
 src/backend/access/heap/pruneheap.c       |  9 ++++-
 src/backend/access/heap/vacuumlazy.c      | 13 ++++++
 src/backend/access/heap/visibilitymap.c   | 20 ++++++++-
 src/backend/access/nbtree/nbtdedup.c      |  4 ++
 src/backend/access/nbtree/nbtinsert.c     | 13 +++++-
 src/backend/access/nbtree/nbtpage.c       | 24 +++++++++++
 src/backend/access/spgist/spgdoinsert.c   | 19 +++++++++
 src/backend/access/spgist/spgvacuum.c     | 13 ++++++
 src/backend/access/transam/multixact.c    |  6 ++-
 src/backend/access/transam/twophase.c     | 10 +++++
 src/backend/access/transam/varsup.c       |  4 ++
 src/backend/access/transam/walprohibit.c  | 10 +++++
 src/backend/access/transam/xact.c         |  7 ++++
 src/backend/access/transam/xlog.c         | 27 +++++++++----
 src/backend/access/transam/xloginsert.c   | 13 +++++-
 src/backend/commands/sequence.c           | 16 ++++++++
 src/backend/commands/variable.c           |  9 +++--
 src/backend/postmaster/checkpointer.c     |  4 ++
 src/backend/storage/buffer/bufmgr.c       | 10 +++--
 src/backend/storage/freespace/freespace.c | 11 ++++-
 src/backend/storage/lmgr/lock.c           |  6 +--
 src/backend/utils/cache/relmapper.c       |  4 ++
 src/include/access/walprohibit.h          | 49 ++++++++++++++++++++++-
 src/include/miscadmin.h                   | 27 +++++++++++++
 40 files changed, 491 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 7db3ae5ee0c..ef002a51773 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -24,6 +24,7 @@
 #include "access/relscan.h"
 #include "access/table.h"
 #include "access/tableam.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "catalog/index.h"
 #include "catalog/pg_am.h"
@@ -758,6 +759,9 @@ brinbuildempty(Relation index)
 		ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
 	LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
 
+	/* Building indexes will have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* Initialize and xlog metabuffer. */
 	START_CRIT_SECTION();
 	brin_metapage_init(BufferGetPage(metabuf), BrinGetPagesPerRange(index),
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 87de0b855b5..197e1213137 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -14,6 +14,7 @@
 #include "access/brin_pageops.h"
 #include "access/brin_revmap.h"
 #include "access/brin_xlog.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -176,6 +177,10 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
 	if (((BrinPageFlags(oldpage) & BRIN_EVACUATE_PAGE) == 0) &&
 		brin_can_do_samepage_update(oldbuf, origsz, newsz))
 	{
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (RelationNeedsWAL(idxrel))
+			CheckWALPermitted();
+
 		START_CRIT_SECTION();
 		if (!PageIndexTupleOverwrite(oldpage, oldoff, (Item) unconstify(BrinTuple *, newtup), newsz))
 			elog(ERROR, "failed to replace BRIN tuple");
@@ -240,6 +245,10 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
 
 		revmapbuf = brinLockRevmapPageForUpdate(revmap, heapBlk);
 
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (RelationNeedsWAL(idxrel))
+			CheckWALPermitted();
+
 		START_CRIT_SECTION();
 
 		/*
@@ -405,6 +414,10 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
 	page = BufferGetPage(*buffer);
 	blk = BufferGetBlockNumber(*buffer);
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(idxrel))
+		CheckWALPermitted();
+
 	/* Execute the actual insertion */
 	START_CRIT_SECTION();
 	if (extended)
@@ -881,6 +894,9 @@ brin_initialize_empty_new_buffer(Relation idxrel, Buffer buffer)
 			   "brin_initialize_empty_new_buffer: initializing blank page %u",
 			   BufferGetBlockNumber(buffer)));
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	CheckWALPermitted();
+
 	START_CRIT_SECTION();
 	page = BufferGetPage(buffer);
 	brin_page_init(page, BRIN_PAGETYPE_REGULAR);
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index e8b8308f82e..b2d286404a2 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -26,6 +26,7 @@
 #include "access/brin_tuple.h"
 #include "access/brin_xlog.h"
 #include "access/rmgr.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -397,6 +398,10 @@ brinRevmapDesummarizeRange(Relation idxrel, BlockNumber heapBlk)
 	 * crashed or aborted summarization; remove them silently.
 	 */
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(idxrel))
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	ItemPointerSetInvalid(&invalidIptr);
@@ -606,6 +611,9 @@ revmap_physical_extend(BrinRevmap *revmap)
 		return;
 	}
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/*
 	 * Ok, we have now locked the metapage and the target block. Re-initialize
 	 * the target block as a revmap page, and update the metapage.
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 8d08b05f515..1b835b3000b 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -16,6 +16,7 @@
 
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
 #include "storage/predicate.h"
@@ -333,6 +334,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 {
 	Page		page = BufferGetPage(stack->buffer);
 	bool		result;
+	bool		needwal;
 	GinPlaceToPageRC rc;
 	uint16		xlflags = 0;
 	Page		childpage = NULL;
@@ -378,6 +380,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 								 insertdata, updateblkno,
 								 &ptp_workspace,
 								 &newlpage, &newrpage);
+	needwal = RelationNeedsWAL(btree->index) && !btree->isBuild;
 
 	if (rc == GPTP_NO_WORK)
 	{
@@ -386,10 +389,14 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 	}
 	else if (rc == GPTP_INSERT)
 	{
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (needwal)
+			CheckWALPermitted();
+
 		/* It will fit, perform the insertion */
 		START_CRIT_SECTION();
 
-		if (RelationNeedsWAL(btree->index) && !btree->isBuild)
+		if (needwal)
 		{
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, stack->buffer, REGBUF_STANDARD);
@@ -410,7 +417,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 			MarkBufferDirty(childbuf);
 		}
 
-		if (RelationNeedsWAL(btree->index) && !btree->isBuild)
+		if (needwal)
 		{
 			XLogRecPtr	recptr;
 			ginxlogInsert xlrec;
@@ -548,6 +555,10 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 			}
 		}
 
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (needwal)
+			CheckWALPermitted();
+
 		/*
 		 * OK, we have the new contents of the left page in a temporary copy
 		 * now (newlpage), and likewise for the new contents of the
@@ -588,7 +599,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 		}
 
 		/* write WAL record */
-		if (RelationNeedsWAL(btree->index) && !btree->isBuild)
+		if (needwal)
 		{
 			XLogRecPtr	recptr;
 
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index 7a2690e97f2..226cb3ce44b 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -16,6 +16,7 @@
 
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
@@ -836,7 +837,11 @@ ginVacuumPostingTreeLeaf(Relation indexrel, Buffer buffer, GinVacuumState *gvs)
 		}
 
 		if (RelationNeedsWAL(indexrel))
+		{
+			/* Can reach here from VACUUM, so need not have an XID */
+			CheckWALPermitted();
 			computeLeafRecompressWALData(leaf);
+		}
 
 		/* Apply changes to page */
 		START_CRIT_SECTION();
@@ -1777,6 +1782,7 @@ createPostingTree(Relation index, ItemPointerData *items, uint32 nitems,
 	int			nrootitems;
 	int			rootsize;
 	bool		is_build = (buildStats != NULL);
+	bool		needwal;
 
 	/* Construct the new root page in memory first. */
 	tmppage = (Page) palloc(BLCKSZ);
@@ -1825,12 +1831,18 @@ createPostingTree(Relation index, ItemPointerData *items, uint32 nitems,
 	 */
 	PredicateLockPageSplit(index, BufferGetBlockNumber(entrybuffer), blkno);
 
+	needwal = RelationNeedsWAL(index) && !is_build;
+
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (needwal)
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	PageRestoreTempPage(tmppage, page);
 	MarkBufferDirty(buffer);
 
-	if (RelationNeedsWAL(index) && !is_build)
+	if (needwal)
 	{
 		XLogRecPtr	recptr;
 		ginxlogCreatePostingTree data;
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 2e41b34d8d5..d7781de7674 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -20,6 +20,7 @@
 
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
+#include "access/walprohibit.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "catalog/pg_am.h"
@@ -68,6 +69,9 @@ writeListPage(Relation index, Buffer buffer,
 	PGAlignedBlock workspace;
 	char	   *ptr;
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	START_CRIT_SECTION();
 
 	GinInitBuffer(buffer, GIN_LIST);
@@ -587,7 +591,11 @@ shiftList(Relation index, Buffer metabuffer, BlockNumber newHead,
 		 * critical section.
 		 */
 		if (RelationNeedsWAL(index))
+		{
+			/* Can reach here from VACUUM, so need not have an XID */
+			CheckWALPermitted();
 			XLogEnsureRecordSpace(data.ndeleted, 0);
+		}
 
 		START_CRIT_SECTION();
 
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 77433dc8a41..d957aa6e582 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -17,6 +17,7 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/tableam.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "catalog/index.h"
 #include "miscadmin.h"
@@ -447,6 +448,9 @@ ginbuildempty(Relation index)
 		ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
 	LockBuffer(RootBuffer, BUFFER_LOCK_EXCLUSIVE);
 
+	/* Building indexes will have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* Initialize and xlog metabuffer and root buffer. */
 	START_CRIT_SECTION();
 	GinInitMetabuffer(MetaBuffer);
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index a400f1fedbc..938089238da 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -17,6 +17,7 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/reloptions.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
@@ -658,12 +659,19 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
 	Buffer		metabuffer;
 	Page		metapage;
 	GinMetaPageData *metadata;
+	bool		needwal;
 
 	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
 	LockBuffer(metabuffer, GIN_EXCLUSIVE);
 	metapage = BufferGetPage(metabuffer);
 	metadata = GinPageGetMeta(metapage);
 
+	needwal = RelationNeedsWAL(index) && !is_build;
+
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (needwal)
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	metadata->nTotalPages = stats->nTotalPages;
@@ -683,7 +691,7 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
 
 	MarkBufferDirty(metabuffer);
 
-	if (RelationNeedsWAL(index) && !is_build)
+	if (needwal)
 	{
 		XLogRecPtr	recptr;
 		ginxlogUpdateMeta data;
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 8ae4fd95a7b..36a884af597 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -16,6 +16,7 @@
 
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
@@ -159,6 +160,10 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
 	 */
 	PredicateLockPageCombine(gvs->index, deleteBlkno, rightlink);
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(gvs->index))
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/* Unlink the page by changing left sibling's rightlink */
@@ -650,6 +655,10 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 		if (resPage)
 		{
+			/* Can reach here from VACUUM, so need not have an XID */
+			if (RelationNeedsWAL(gvs.index))
+				CheckWALPermitted();
+
 			START_CRIT_SECTION();
 			PageRestoreTempPage(resPage, page);
 			MarkBufferDirty(buffer);
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 79fe6eb8d62..8f6b15d8ee4 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -16,6 +16,7 @@
 
 #include "access/gist_private.h"
 #include "access/gistscan.h"
+#include "access/walprohibit.h"
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
@@ -134,6 +135,9 @@ gistbuildempty(Relation index)
 	buffer = ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 
+	/* Building indexes will have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* Initialize and xlog buffer */
 	START_CRIT_SECTION();
 	GISTInitBuffer(buffer, F_LEAF);
@@ -467,6 +471,10 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
 		if (!is_build && RelationNeedsWAL(rel))
 			XLogEnsureRecordSpace(npage, 1 + npage * 2);
 
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (RelationNeedsWAL(rel))
+			CheckWALPermitted();
+
 		START_CRIT_SECTION();
 
 		/*
@@ -525,6 +533,10 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
 	}
 	else
 	{
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (RelationNeedsWAL(rel))
+			CheckWALPermitted();
+
 		/*
 		 * Enough space.  We always get here if ntup==0.
 		 */
@@ -1665,6 +1677,10 @@ gistprunepage(Relation rel, Page page, Buffer buffer, Relation heapRel)
 
 	if (ndeletable > 0)
 	{
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (RelationNeedsWAL(rel))
+			CheckWALPermitted();
+
 		START_CRIT_SECTION();
 
 		PageIndexMultiDelete(page, deletable, ndeletable);
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index a9c616c7724..ccf9bc0c214 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/gist_private.h"
 #include "access/transam.h"
+#include "access/walprohibit.h"
 #include "commands/vacuum.h"
 #include "lib/integerset.h"
 #include "miscadmin.h"
@@ -341,6 +342,10 @@ restart:
 		 */
 		if (ntodelete > 0)
 		{
+			/* Can reach here from VACUUM, so need not have an XID */
+			if (RelationNeedsWAL(rel))
+				CheckWALPermitted();
+
 			START_CRIT_SECTION();
 
 			MarkBufferDirty(buffer);
@@ -634,6 +639,10 @@ gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	 */
 	txid = ReadNextFullTransactionId();
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(info->index))
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/* mark the page as deleted */
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d528e77..1d3f4c92f19 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -22,6 +22,7 @@
 #include "access/hash_xlog.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/walprohibit.h"
 #include "catalog/index.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -572,6 +573,10 @@ loop_top:
 		goto loop_top;
 	}
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(rel))
+		CheckWALPermitted();
+
 	/* Okay, we're really done.  Update tuple count in metapage. */
 	START_CRIT_SECTION();
 
@@ -787,6 +792,10 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 		 */
 		if (ndeletable > 0)
 		{
+			/* Can reach here from VACUUM, so need not have an XID */
+			if (RelationNeedsWAL(rel))
+				CheckWALPermitted();
+
 			/* No ereport(ERROR) until changes are logged */
 			START_CRIT_SECTION();
 
@@ -882,6 +891,10 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 		page = BufferGetPage(bucket_buf);
 		bucket_opaque = (HashPageOpaque) PageGetSpecialPointer(page);
 
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (RelationNeedsWAL(rel))
+			CheckWALPermitted();
+
 		/* No ereport(ERROR) until changes are logged */
 		START_CRIT_SECTION();
 
diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 2ebe671967b..360e30456fe 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -17,6 +17,7 @@
 
 #include "access/hash.h"
 #include "access/hash_xlog.h"
+#include "access/walprohibit.h"
 #include "miscadmin.h"
 #include "storage/buf_internals.h"
 #include "storage/lwlock.h"
@@ -193,6 +194,9 @@ restart_insert:
 	 */
 	LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* Do the update.  No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
@@ -370,6 +374,10 @@ _hash_vacuum_one_page(Relation rel, Relation hrel, Buffer metabuf, Buffer buf)
 		 */
 		LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
 
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (RelationNeedsWAL(rel))
+			CheckWALPermitted();
+
 		/* No ereport(ERROR) until changes are logged */
 		START_CRIT_SECTION();
 
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 00f0a940116..5abba14899e 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -19,6 +19,7 @@
 
 #include "access/hash.h"
 #include "access/hash_xlog.h"
+#include "access/walprohibit.h"
 #include "miscadmin.h"
 #include "utils/rel.h"
 
@@ -312,6 +313,9 @@ _hash_addovflpage(Relation rel, Buffer metabuf, Buffer buf, bool retain_pin)
 
 found:
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/*
 	 * Do the update.  No ereport(ERROR) until changes are logged. We want to
 	 * log the changes for bitmap page and overflow page together to avoid
@@ -577,6 +581,10 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
 	if (RelationNeedsWAL(rel))
 		XLogEnsureRecordSpace(HASH_XLOG_FREE_OVFL_BUFS, 4 + nitups);
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(rel))
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/*
@@ -929,7 +937,13 @@ readpage:
 					 * WAL for that.
 					 */
 					if (RelationNeedsWAL(rel))
+					{
+						/*
+						 * Can reach here from VACUUM, so need not have an XID
+						 */
+						CheckWALPermitted();
 						XLogEnsureRecordSpace(0, 3 + nitups);
+					}
 
 					START_CRIT_SECTION();
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index a664ecf494a..faad58297d2 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -30,6 +30,7 @@
 
 #include "access/hash.h"
 #include "access/hash_xlog.h"
+#include "access/walprohibit.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "storage/lmgr.h"
@@ -816,6 +817,9 @@ restart_expand:
 		goto fail;
 	}
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/*
 	 * Since we are scribbling on the pages in the shared buffers, establish a
 	 * critical section.  Any failure in this next code leaves us with a big
@@ -1172,6 +1176,9 @@ _hash_splitbucket(Relation rel,
 
 				if (PageGetFreeSpaceForMultipleTuples(npage, nitups + 1) < (all_tups_size + itemsz))
 				{
+					/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+					AssertWALPermitted_HaveXID();
+
 					/*
 					 * Change the shared buffer state in critical section,
 					 * otherwise any error could make it unrecoverable.
@@ -1223,6 +1230,9 @@ _hash_splitbucket(Relation rel,
 		/* Exit loop if no more overflow pages in old bucket */
 		if (!BlockNumberIsValid(oblkno))
 		{
+			/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+			AssertWALPermitted_HaveXID();
+
 			/*
 			 * Change the shared buffer state in critical section, otherwise
 			 * any error could make it unrecoverable.
@@ -1269,6 +1279,9 @@ _hash_splitbucket(Relation rel,
 	npage = BufferGetPage(bucket_nbuf);
 	nopaque = (HashPageOpaque) PageGetSpecialPointer(npage);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	START_CRIT_SECTION();
 
 	oopaque->hasho_flag &= ~LH_BUCKET_BEING_SPLIT;
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index d881f4cd46a..f8cafc378ff 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -46,6 +46,7 @@
 #include "access/transam.h"
 #include "access/valid.h"
 #include "access/visibilitymap.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
@@ -1870,6 +1871,9 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	 */
 	CheckForSerializableConflictIn(relation, NULL, InvalidBlockNumber);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -2143,6 +2147,9 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 										   &vmbuffer, NULL);
 		page = BufferGetPage(buffer);
 
+		/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+		AssertWALPermitted_HaveXID();
+
 		/* NO EREPORT(ERROR) from here till changes are logged */
 		START_CRIT_SECTION();
 
@@ -2661,6 +2668,9 @@ l1:
 							  xid, LockTupleExclusive, true,
 							  &new_xmax, &new_infomask, &new_infomask2);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	START_CRIT_SECTION();
 
 	/*
@@ -3413,6 +3423,9 @@ l2:
 
 		Assert(HEAP_XMAX_IS_LOCKED_ONLY(infomask_lock_old_tuple));
 
+		/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+		AssertWALPermitted_HaveXID();
+
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
@@ -3586,6 +3599,9 @@ l2:
 										   bms_overlap(modified_attrs, id_attrs),
 										   &old_key_copied);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -4519,6 +4535,9 @@ failed:
 							  GetCurrentTransactionId(), mode, false,
 							  &xid, &new_infomask, &new_infomask2);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	START_CRIT_SECTION();
 
 	/*
@@ -5310,6 +5329,9 @@ l4:
 								VISIBILITYMAP_ALL_FROZEN))
 			cleared_all_frozen = true;
 
+		/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+		AssertWALPermitted_HaveXID();
+
 		START_CRIT_SECTION();
 
 		/* ... and set them */
@@ -5468,6 +5490,9 @@ heap_finish_speculative(Relation relation, ItemPointer tid)
 	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
 					 "invalid speculative token constant");
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -5576,6 +5601,9 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 		elog(ERROR, "attempted to kill a non-speculative tuple");
 	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/*
 	 * No need to check for serializable conflicts here.  There is never a
 	 * need for a combocid, either.  No need to extract replica identity, or
@@ -5722,6 +5750,10 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
 	if (oldlen != newlen || htup->t_hoff != tuple->t_data->t_hoff)
 		elog(ERROR, "wrong tuple length");
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(relation))
+		CheckWALPermitted();
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 256df4de105..90f43cbcb9b 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -18,6 +18,7 @@
 #include "access/heapam_xlog.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
+#include "access/walprohibit.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "miscadmin.h"
@@ -77,11 +78,11 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
 	TransactionId OldestXmin;
 
 	/*
-	 * We can't write WAL in recovery mode, so there's no point trying to
+	 * We can't write WAL during read-only mode, so there's no point trying to
 	 * clean the page. The primary will likely issue a cleaning WAL record soon
 	 * anyway, so this is no particular loss.
 	 */
-	if (RecoveryInProgress())
+	if (!XLogInsertAllowed())
 		return;
 
 	/*
@@ -225,6 +226,10 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
 									 &prstate);
 	}
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(relation))
+		CheckWALPermitted();
+
 	/* Any error while applying the changes is critical */
 	START_CRIT_SECTION();
 
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 1bbc4598f75..2e5202d8068 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -59,6 +59,7 @@
 #include "access/parallel.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
@@ -1203,6 +1204,10 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
 			 */
 			if (!PageIsAllVisible(page))
 			{
+				/* Can reach here from VACUUM, so need not have an XID */
+				if (RelationNeedsWAL(onerel))
+					CheckWALPermitted();
+
 				START_CRIT_SECTION();
 
 				/* mark buffer dirty before writing a WAL record */
@@ -1471,6 +1476,10 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
 		 */
 		if (nfrozen > 0)
 		{
+			/* Can reach here from VACUUM, so need not have an XID */
+			if (RelationNeedsWAL(onerel))
+				CheckWALPermitted();
+
 			START_CRIT_SECTION();
 
 			MarkBufferDirty(buf);
@@ -1917,6 +1926,10 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
 	update_vacuum_error_info(vacrelstats, &saved_err_info, VACUUM_ERRCB_PHASE_VACUUM_HEAP,
 							 blkno);
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(onerel))
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	for (; tupindex < dead_tuples->num_tuples; tupindex++)
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 0a51678c40d..30d1d6f34c7 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -88,6 +88,7 @@
 
 #include "access/heapam_xlog.h"
 #include "access/visibilitymap.h"
+#include "access/walprohibit.h"
 #include "access/xlog.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
@@ -270,6 +271,16 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf,
 	map = (uint8 *) PageGetContents(page);
 	LockBuffer(vmBuf, BUFFER_LOCK_EXCLUSIVE);
 
+	/*
+	 * Can reach here from VACUUM or from startup process, so need not have an
+	 * XID.
+	 *
+	 * Recovery in the startup process never have wal prohibit state, skip
+	 * permission check if reach here in the startup process.
+	 */
+	if (RelationNeedsWAL(rel))
+		InRecovery ? AssertWALPermitted() : CheckWALPermitted();
+
 	if (flags != (map[mapByte] >> mapOffset & VISIBILITYMAP_VALID_BITS))
 	{
 		START_CRIT_SECTION();
@@ -476,6 +487,7 @@ visibilitymap_prepare_truncate(Relation rel, BlockNumber nheapblocks)
 		Buffer		mapBuffer;
 		Page		page;
 		char	   *map;
+		bool		needwal;
 
 		newnblocks = truncBlock + 1;
 
@@ -489,8 +501,14 @@ visibilitymap_prepare_truncate(Relation rel, BlockNumber nheapblocks)
 		page = BufferGetPage(mapBuffer);
 		map = PageGetContents(page);
 
+		needwal = (!InRecovery && RelationNeedsWAL(rel) && XLogHintBitIsNeeded());
+
 		LockBuffer(mapBuffer, BUFFER_LOCK_EXCLUSIVE);
 
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (needwal)
+			CheckWALPermitted();
+
 		/* NO EREPORT(ERROR) from here till changes are logged */
 		START_CRIT_SECTION();
 
@@ -518,7 +536,7 @@ visibilitymap_prepare_truncate(Relation rel, BlockNumber nheapblocks)
 		 * during recovery.
 		 */
 		MarkBufferDirty(mapBuffer);
-		if (!InRecovery && RelationNeedsWAL(rel) && XLogHintBitIsNeeded())
+		if (needwal)
 			log_newpage_buffer(mapBuffer, false);
 
 		END_CRIT_SECTION();
diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c
index f6be865b17e..a471a4b7ff5 100644
--- a/src/backend/access/nbtree/nbtdedup.c
+++ b/src/backend/access/nbtree/nbtdedup.c
@@ -16,6 +16,7 @@
 
 #include "access/nbtree.h"
 #include "access/nbtxlog.h"
+#include "access/walprohibit.h"
 #include "miscadmin.h"
 #include "utils/rel.h"
 
@@ -271,6 +272,9 @@ _bt_dedup_one_page(Relation rel, Buffer buf, Relation heapRel,
 		nopaque->btpo_flags &= ~BTP_HAS_GARBAGE;
 	}
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	START_CRIT_SECTION();
 
 	PageRestoreTempPage(newpage, page);
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index b86c122763e..3527c9f4183 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -19,6 +19,7 @@
 #include "access/nbtxlog.h"
 #include "access/tableam.h"
 #include "access/transam.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
@@ -1246,6 +1247,9 @@ _bt_insertonpg(Relation rel,
 			}
 		}
 
+		/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+		AssertWALPermitted_HaveXID();
+
 		/* Do the update.  No ereport(ERROR) until changes are logged */
 		START_CRIT_SECTION();
 
@@ -1900,13 +1904,17 @@ _bt_split(Relation rel, BTScanInsert itup_key, Buffer buf, Buffer cbuf,
 			ropaque->btpo_flags |= BTP_SPLIT_END;
 	}
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/*
 	 * Right sibling is locked, new siblings are prepared, but original page
 	 * is not updated yet.
 	 *
 	 * NO EREPORT(ERROR) till right sibling is updated.  We can get away with
 	 * not starting the critical section till here because we haven't been
-	 * scribbling on the original page yet; see comments above.
+	 * scribbling on the original page yet; see the comments above for grabbing
+	 * the right sibling.
 	 */
 	START_CRIT_SECTION();
 
@@ -2469,6 +2477,9 @@ _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf)
 	right_item = CopyIndexTuple(item);
 	BTreeTupleSetDownLink(right_item, rbkno);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* NO EREPORT(ERROR) from here till newroot op is logged */
 	START_CRIT_SECTION();
 
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 75628e0eb98..09b45fbb559 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -26,6 +26,7 @@
 #include "access/nbtxlog.h"
 #include "access/tableam.h"
 #include "access/transam.h"
+#include "access/walprohibit.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
@@ -201,6 +202,10 @@ _bt_update_meta_cleanup_info(Relation rel, TransactionId oldestBtpoXact,
 	LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);
 	LockBuffer(metabuf, BT_WRITE);
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(rel))
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/* upgrade meta-page if needed */
@@ -376,6 +381,10 @@ _bt_getroot(Relation rel, int access)
 		/* Get raw page pointer for metapage */
 		metapg = BufferGetPage(metabuf);
 
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (RelationNeedsWAL(rel))
+			CheckWALPermitted();
+
 		/* NO ELOG(ERROR) till meta is updated */
 		START_CRIT_SECTION();
 
@@ -1068,6 +1077,10 @@ _bt_delitems_vacuum(Relation rel, Buffer buf,
 		}
 	}
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(rel))
+		CheckWALPermitted();
+
 	/* No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
@@ -1195,6 +1208,9 @@ _bt_delitems_delete(Relation rel, Buffer buf,
 		latestRemovedXid =
 			_bt_xid_horizon(rel, heapRel, page, deletable, ndeletable);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
@@ -1812,6 +1828,10 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
 	 */
 	PredicateLockPageCombine(rel, leafblkno, leafrightsib);
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(rel))
+		CheckWALPermitted();
+
 	/* No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
@@ -2168,6 +2188,10 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 	 * Here we begin doing the deletion.
 	 */
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(rel))
+		CheckWALPermitted();
+
 	/* No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 934d65b89f2..003b5e80f21 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/spgist_private.h"
 #include "access/spgxlog.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -214,6 +215,9 @@ addLeafTuple(Relation index, SpGistState *state, SpGistLeafTuple leafTuple,
 	xlrec.offnumParent = InvalidOffsetNumber;
 	xlrec.nodeI = 0;
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	START_CRIT_SECTION();
 
 	if (current->offnum == InvalidOffsetNumber ||
@@ -458,6 +462,9 @@ moveLeafs(Relation index, SpGistState *state,
 
 	leafdata = leafptr = palloc(size);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	START_CRIT_SECTION();
 
 	/* copy all the old tuples to new page, unless they're dead */
@@ -1109,6 +1116,9 @@ doPickSplit(Relation index, SpGistState *state,
 
 	leafdata = leafptr = (char *) palloc(totalLeafSizes);
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	/* Here we begin making the changes to the target pages */
 	START_CRIT_SECTION();
 
@@ -1517,6 +1527,9 @@ spgAddNodeAction(Relation index, SpGistState *state,
 	if (PageGetExactFreeSpace(current->page) >=
 		newInnerTuple->size - innerTuple->size)
 	{
+		/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+		AssertWALPermitted_HaveXID();
+
 		/*
 		 * We can replace the inner tuple by new version in-place
 		 */
@@ -1603,6 +1616,9 @@ spgAddNodeAction(Relation index, SpGistState *state,
 		else
 			xlrec.parentBlk = 2;
 
+		/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+		AssertWALPermitted_HaveXID();
+
 		START_CRIT_SECTION();
 
 		/* insert new ... */
@@ -1788,6 +1804,9 @@ spgSplitNodeAction(Relation index, SpGistState *state,
 									&xlrec.newPage);
 	}
 
+	/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	START_CRIT_SECTION();
 
 	/*
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index bd98707f3c0..39bace9e490 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -19,6 +19,7 @@
 #include "access/spgist_private.h"
 #include "access/spgxlog.h"
 #include "access/transam.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "catalog/storage_xlog.h"
 #include "commands/vacuum.h"
@@ -323,6 +324,10 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 	if (nDeletable != xlrec.nDead + xlrec.nPlaceholder + xlrec.nMove)
 		elog(ERROR, "inconsistent counts of deletable tuples");
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(index))
+		CheckWALPermitted();
+
 	/* Do the updates */
 	START_CRIT_SECTION();
 
@@ -447,6 +452,10 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 	if (xlrec.nDelete == 0)
 		return;					/* nothing more to do */
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(index))
+		CheckWALPermitted();
+
 	/* Do the update */
 	START_CRIT_SECTION();
 
@@ -505,6 +514,10 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer)
 	xlrec.nToPlaceholder = 0;
 	xlrec.newestRedirectXid = InvalidTransactionId;
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	if (RelationNeedsWAL(index))
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/*
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index ce84dac0c40..2b7b2ccad31 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -73,6 +73,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/twophase_rmgr.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
@@ -1143,6 +1144,9 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 
 	ExtendMultiXactMember(nextOffset, nmembers);
 
+	/* Can reach here from VACUUM, so need not have an XID */
+	CheckWALPermitted();
+
 	/*
 	 * Critical section from here until caller has written the data into the
 	 * just-reserved SLRU space; we don't want to error out with a partly
@@ -2942,7 +2946,7 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
 	mxtruncinfo trunc;
 	MultiXactId earliest;
 
-	Assert(!RecoveryInProgress());
+	Assert(XLogInsertAllowed());
 	Assert(MultiXactState->finishedStartup);
 
 	/*
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 9b2e59bf0ec..0fa01f241f3 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -82,6 +82,7 @@
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/twophase_rmgr.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
@@ -1112,6 +1113,9 @@ EndPrepare(GlobalTransaction gxact)
 	 */
 	XLogEnsureRecordSpace(0, records.num_chunks);
 
+	/* Recording transaction prepares, so we'll have an XID */
+	AssertWALPermitted_HaveXID();
+
 	START_CRIT_SECTION();
 
 	MyProc->delayChkpt = true;
@@ -2204,6 +2208,9 @@ RecordTransactionCommitPrepared(TransactionId xid,
 	replorigin = (replorigin_session_origin != InvalidRepOriginId &&
 				  replorigin_session_origin != DoNotReplicateId);
 
+	/* COMMIT PREPARED need not have an XID */
+	CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/* See notes in RecordTransactionCommit */
@@ -2294,6 +2301,9 @@ RecordTransactionAbortPrepared(TransactionId xid,
 		elog(PANIC, "cannot abort transaction %u, it was already committed",
 			 xid);
 
+	/* ROLLBACK PREPARED need not have an XID */
+	CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/*
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index e14b53bf9e3..365de44321d 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -17,6 +17,7 @@
 #include "access/commit_ts.h"
 #include "access/subtrans.h"
 #include "access/transam.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "commands/dbcommands.h"
@@ -73,6 +74,9 @@ GetNewTransactionId(bool isSubXact)
 	if (RecoveryInProgress())
 		elog(ERROR, "cannot assign TransactionIds during recovery");
 
+	/* Cannot assign transaction id in read-only mode */
+	CheckWALPermitted();
+
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 
 	full_xid = ShmemVariableCache->nextFullXid;
diff --git a/src/backend/access/transam/walprohibit.c b/src/backend/access/transam/walprohibit.c
index a8cda2fafbc..896f0917cef 100644
--- a/src/backend/access/transam/walprohibit.c
+++ b/src/backend/access/transam/walprohibit.c
@@ -16,6 +16,16 @@
 #include "postmaster/bgwriter.h"
 #include "storage/procsignal.h"
 
+/*
+ * Assert flag to enforce WAL insert permission check rule before starting a
+ * critical section for the WAL writes.  For this, either of CheckWALPermitted,
+ * AssertWALPermitted_HaveXID, or AssertWALPermitted must be called before
+ * starting the critical section.
+ */
+#ifdef USE_ASSERT_CHECKING
+WALPermitCheckState walpermit_checked_state = WALPERMIT_UNCHECKED;
+#endif
+
 /*
  * ProcessBarrierWALProhibit()
  *
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4fbfcdbb965..02830cbf85d 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -26,6 +26,7 @@
 #include "access/subtrans.h"
 #include "access/transam.h"
 #include "access/twophase.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
@@ -1292,6 +1293,9 @@ RecordTransactionCommit(void)
 		/* Tell bufmgr and smgr to prepare for commit */
 		BufmgrCommit();
 
+		/* We'll be reaching here with valid XID only. */
+		AssertWALPermitted_HaveXID();
+
 		/*
 		 * Mark ourselves as within our "commit critical section".  This
 		 * forces any concurrent checkpoint to wait until we've updated
@@ -1652,6 +1656,9 @@ RecordTransactionAbort(bool isSubXact)
 		elog(PANIC, "cannot abort transaction %u, it was already committed",
 			 xid);
 
+	/* We'll be reaching here with valid XID only. */
+	AssertWALPermitted_HaveXID();
+
 	/* Fetch the data we need for the abort record */
 	nrels = smgrGetPendingDeletes(false, &rels);
 	nchildren = xactGetCommittedChildren(&children);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 64cc347caa2..d5d184b3a50 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -1027,7 +1027,7 @@ XLogInsertRecord(XLogRecData *rdata,
 
 	/* cross-check on whether we should be here or not */
 	if (!XLogInsertAllowed())
-		elog(ERROR, "cannot make new WAL entries during recovery");
+		elog(ERROR, "cannot make new WAL entries now");
 
 	/*----------
 	 *
@@ -2862,9 +2862,11 @@ XLogFlush(XLogRecPtr record)
 	 * trying to flush the WAL, we should update minRecoveryPoint instead. We
 	 * test XLogInsertAllowed(), not InRecovery, because we need checkpointer
 	 * to act this way too, and because when it tries to write the
-	 * end-of-recovery checkpoint, it should indeed flush.
+	 * end-of-recovery checkpoint, it should indeed flush. Also, WAL prohibit
+	 * state should not restrict WAL flushing. Otherwise, the dirty buffer
+	 * cannot be evicted until WAL has been flushed up to the buffer's LSN.
 	 */
-	if (!XLogInsertAllowed())
+	if (!XLogInsertAllowed() && !IsWALProhibited())
 	{
 		UpdateMinRecoveryPoint(record, false);
 		return;
@@ -8836,6 +8838,8 @@ CreateCheckPoint(int flags)
 	/* sanity check */
 	if (RecoveryInProgress() && (flags & CHECKPOINT_END_OF_RECOVERY) == 0)
 		elog(ERROR, "can't create a checkpoint during recovery");
+	if (!XLogInsertAllowed() && !RecoveryInProgress())
+		elog(ERROR, "can't create a checkpoint while system is read only ");
 
 	/*
 	 * Initialize InitXLogInsert working areas before entering the critical
@@ -8865,6 +8869,8 @@ CreateCheckPoint(int flags)
 	MemSet(&CheckpointStats, 0, sizeof(CheckpointStats));
 	CheckpointStats.ckpt_start_t = GetCurrentTimestamp();
 
+	AssertWALPermitted();
+
 	/*
 	 * Use a critical section to force system panic if we have trouble.
 	 */
@@ -9093,6 +9099,8 @@ CreateCheckPoint(int flags)
 	if (!shutdown && XLogStandbyInfoActive())
 		LogStandbySnapshot();
 
+	AssertWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/*
@@ -9250,6 +9258,8 @@ CreateEndOfRecoveryRecord(void)
 
 	LocalSetXLogInsertAllowed();
 
+	AssertWALPermitted();
+
 	START_CRIT_SECTION();
 
 	XLogBeginInsert();
@@ -9890,7 +9900,7 @@ void
 UpdateFullPageWrites(void)
 {
 	XLogCtlInsert *Insert = &XLogCtl->Insert;
-	bool		recoveryInProgress;
+	bool		WALInsertAllowed;
 
 	/*
 	 * Do nothing if full_page_writes has not been changed.
@@ -9904,10 +9914,10 @@ UpdateFullPageWrites(void)
 
 	/*
 	 * Perform this outside critical section so that the WAL insert
-	 * initialization done by RecoveryInProgress() doesn't trigger an
+	 * initialization done by XLogInsertAllowed() doesn't trigger an
 	 * assertion failure.
 	 */
-	recoveryInProgress = RecoveryInProgress();
+	WALInsertAllowed = XLogInsertAllowed();
 
 	START_CRIT_SECTION();
 
@@ -9929,8 +9939,11 @@ UpdateFullPageWrites(void)
 	 * Write an XLOG_FPW_CHANGE record. This allows us to keep track of
 	 * full_page_writes during archive recovery, if required.
 	 */
-	if (XLogStandbyInfoActive() && !recoveryInProgress)
+	if (XLogStandbyInfoActive() && WALInsertAllowed)
 	{
+		/* Assured that WAL permission has been checked */
+		AssertWALPermitted();
+
 		XLogBeginInsert();
 		XLogRegisterData((char *) (&fullPageWrites), sizeof(bool));
 
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index b21679f09eb..d69f6ca427a 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -19,6 +19,7 @@
 
 #include "postgres.h"
 
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
@@ -124,9 +125,14 @@ XLogBeginInsert(void)
 	Assert(mainrdata_last == (XLogRecData *) &mainrdata_head);
 	Assert(mainrdata_len == 0);
 
+	/*
+	 * WAL permission must have checked before entering the critical section.
+	 * Otherwise, WAL prohibited error will force system panic.
+	 */
+	Assert(walpermit_checked_state != WALPERMIT_UNCHECKED || !CritSectionCount);
+
 	/* cross-check on whether we should be here or not */
-	if (!XLogInsertAllowed())
-		elog(ERROR, "cannot make new WAL entries during recovery");
+	CheckWALPermitted();
 
 	if (begininsert_called)
 		elog(ERROR, "XLogBeginInsert was already called");
@@ -204,6 +210,9 @@ XLogResetInsertion(void)
 	mainrdata_last = (XLogRecData *) &mainrdata_head;
 	curinsert_flags = 0;
 	begininsert_called = false;
+
+	/* Reset walpermit_checked flag */
+	RESET_WALPERMIT_CHECKED_STATE();
 }
 
 /*
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73bfd44..f961178b358 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -20,6 +20,7 @@
 #include "access/relation.h"
 #include "access/table.h"
 #include "access/transam.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
@@ -378,8 +379,13 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* check the comment above nextval_internal()'s equivalent call. */
 	if (RelationNeedsWAL(rel))
+	{
 		GetTopTransactionId();
 
+		/* Cannot have valid XID without WAL permission */
+		AssertWALPermitted_HaveXID();
+	}
+
 	START_CRIT_SECTION();
 
 	MarkBufferDirty(buf);
@@ -766,8 +772,13 @@ nextval_internal(Oid relid, bool check_permissions)
 	 * (Have to do that here, so we're outside the critical section)
 	 */
 	if (logit && RelationNeedsWAL(seqrel))
+	{
 		GetTopTransactionId();
 
+		/* Cannot have valid XID without WAL permission */
+		AssertWALPermitted_HaveXID();
+	}
+
 	/* ready to change the on-disk (or really, in-buffer) tuple */
 	START_CRIT_SECTION();
 
@@ -977,8 +988,13 @@ do_setval(Oid relid, int64 next, bool iscalled)
 
 	/* check the comment above nextval_internal()'s equivalent call. */
 	if (RelationNeedsWAL(seqrel))
+	{
 		GetTopTransactionId();
 
+		/* Cannot have valid XID without WAL permission */
+		AssertWALPermitted_HaveXID();
+	}
+
 	/* ready to change the on-disk (or really, in-buffer) tuple */
 	START_CRIT_SECTION();
 
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 484f7ea2c0e..acc34d2ad7c 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -501,11 +501,14 @@ check_transaction_read_only(bool *newval, void **extra, GucSource source)
 			GUC_check_errmsg("transaction read-write mode must be set before any query");
 			return false;
 		}
-		/* Can't go to r/w mode while recovery is still active */
-		if (RecoveryInProgress())
+		/*
+		 * Can't go to r/w mode while recovery is still active or while in WAL
+		 * prohibit state
+		 */
+		if (!XLogInsertAllowed())
 		{
 			GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
-			GUC_check_errmsg("cannot set transaction read-write mode during recovery");
+			GUC_check_errmsg("cannot set transaction read-write mode while system is read only");
 			return false;
 		}
 	}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index bbe62b73a08..7e3882a055c 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -934,6 +934,10 @@ RequestCheckpoint(int flags)
 	int			old_failed,
 				old_started;
 
+	/* The checkpoint is allowed in recovery but not in WAL prohibit state */
+	if (!RecoveryInProgress())
+		CheckWALPermitted();
+
 	/*
 	 * If in a standalone backend, just do it ourselves.
 	 */
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 29c920800a6..ba74ddcd249 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -3603,13 +3603,15 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std)
 		{
 			/*
 			 * If we must not write WAL, due to a relfilenode-specific
-			 * condition or being in recovery, don't dirty the page.  We can
-			 * set the hint, just not dirty the page as a result so the hint
-			 * is lost when we evict the page or shutdown.
+			 * condition or in general, don't dirty the page.  We can
+			 * set the hint, but must not dirty the page as a result, lest
+			 * we trigger WAL generation. Unless the page is dirtied again
+			 * later, the hint will be lost when the page is evicted, or at
+			 * shutdown.
 			 *
 			 * See src/backend/storage/page/README for longer discussion.
 			 */
-			if (RecoveryInProgress() ||
+			if (!XLogInsertAllowed() ||
 				RelFileNodeSkippingWAL(bufHdr->tag.rnode))
 				return;
 
diff --git a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c
index 95a21f6cc38..5faa69fabb9 100644
--- a/src/backend/storage/freespace/freespace.c
+++ b/src/backend/storage/freespace/freespace.c
@@ -24,6 +24,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/walprohibit.h"
 #include "access/xlogutils.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
@@ -285,12 +286,20 @@ FreeSpaceMapPrepareTruncateRel(Relation rel, BlockNumber nblocks)
 	 */
 	if (first_removed_slot > 0)
 	{
+		bool needwal;
+
 		buf = fsm_readbuf(rel, first_removed_address, false);
 		if (!BufferIsValid(buf))
 			return InvalidBlockNumber;	/* nothing to do; the FSM was already
 										 * smaller */
 		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
+		needwal = (!InRecovery && RelationNeedsWAL(rel) && XLogHintBitIsNeeded());
+
+		/* Can reach here from VACUUM, so need not have an XID */
+		if (needwal)
+			CheckWALPermitted();
+
 		/* NO EREPORT(ERROR) from here till changes are logged */
 		START_CRIT_SECTION();
 
@@ -305,7 +314,7 @@ FreeSpaceMapPrepareTruncateRel(Relation rel, BlockNumber nblocks)
 		 * during recovery.
 		 */
 		MarkBufferDirty(buf);
-		if (!InRecovery && RelationNeedsWAL(rel) && XLogHintBitIsNeeded())
+		if (needwal)
 			log_newpage_buffer(buf, false);
 
 		END_CRIT_SECTION();
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..212312d5ae5 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -794,15 +794,15 @@ LockAcquireExtended(const LOCKTAG *locktag,
 	if (lockmode <= 0 || lockmode > lockMethodTable->numLockModes)
 		elog(ERROR, "unrecognized lock mode: %d", lockmode);
 
-	if (RecoveryInProgress() && !InRecovery &&
+	if (!XLogInsertAllowed() && !InRecovery &&
 		(locktag->locktag_type == LOCKTAG_OBJECT ||
 		 locktag->locktag_type == LOCKTAG_RELATION) &&
 		lockmode > RowExclusiveLock)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("cannot acquire lock mode %s on database objects while recovery is in progress",
+				 errmsg("cannot acquire lock mode %s on database objects while system is read only",
 						lockMethodTable->lockModeNames[lockmode]),
-				 errhint("Only RowExclusiveLock or less can be acquired on database objects during recovery.")));
+				 errhint("Only RowExclusiveLock or less can be acquired on database objects while system is read only")));
 
 #ifdef LOCK_DEBUG
 	if (LOCK_DEBUG_ENABLED(locktag))
diff --git a/src/backend/utils/cache/relmapper.c b/src/backend/utils/cache/relmapper.c
index 671fbb0ed5c..ec48073bbf1 100644
--- a/src/backend/utils/cache/relmapper.c
+++ b/src/backend/utils/cache/relmapper.c
@@ -43,6 +43,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
@@ -841,6 +842,9 @@ write_relmap_file(bool shared, RelMapFile *newmap,
 		xl_relmap_update xlrec;
 		XLogRecPtr	lsn;
 
+		/* Must be performing an INSERT or UPDATE, so we'll have an XID */
+		AssertWALPermitted_HaveXID();
+
 		/* now errors are fatal ... */
 		START_CRIT_SECTION();
 
diff --git a/src/include/access/walprohibit.h b/src/include/access/walprohibit.h
index 163fe0d2fce..1adcfc571d6 100644
--- a/src/include/access/walprohibit.h
+++ b/src/include/access/walprohibit.h
@@ -19,8 +19,8 @@ extern bool ProcessBarrierWALProhibit(void);
 extern void AlterSystemSetWALProhibitState(AlterSystemWALProhibitState *stmt);
 
 /* WAL Prohibit States */
-#define	WALPROHIBIT_STATE_READ_WRITE		0x0000
-#define	WALPROHIBIT_STATE_READ_ONLY			0x0001
+#define	WALPROHIBIT_STATE_READ_WRITE		0x0000	/* WAL permitted */
+#define	WALPROHIBIT_STATE_READ_ONLY			0x0001	/* WAL prohibited */
 
 /*
  * The bit is used in state transition from one state to another.  When this
@@ -29,4 +29,49 @@ extern void AlterSystemSetWALProhibitState(AlterSystemWALProhibitState *stmt);
  */
 #define WALPROHIBIT_TRANSITION_IN_PROGRESS	0x0002
 
+/* Never reaches when WAL is prohibited. */
+static inline void
+AssertWALPermitted(void)
+{
+	/*
+	 * Recovery in the startup process never is in wal prohibited state.
+	 */
+	Assert(InRecovery || XLogInsertAllowed());
+
+#ifdef USE_ASSERT_CHECKING
+	walpermit_checked_state = WALPERMIT_CHECKED;
+#endif
+}
+
+/*
+ * XID-bearing transactions are killed off by "ALTER SYSTEM READ ONLY", so any
+ * part of the code that can only be reached with an XID assigned is never
+ * reached when WAL is prohibited.
+ */
+static inline void
+AssertWALPermitted_HaveXID(void)
+{
+	Assert(FullTransactionIdIsValid(GetTopFullTransactionIdIfAny()));
+	AssertWALPermitted();
+}
+
+/*
+ * In opposite to the above assertion if a transaction doesn't have valid XID
+ * then it won't be killed while changing the system state to WAL prohibited.
+ * Therefore, we need to explicitly error out before entering into the critical
+ * section.
+ */
+static inline void
+CheckWALPermitted(void)
+{
+	if (!XLogInsertAllowed())
+		ereport(ERROR,
+				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
+				 errmsg("system is now read only")));
+
+#ifdef USE_ASSERT_CHECKING
+	walpermit_checked_state = WALPERMIT_CHECKED;
+#endif
+}
+
 #endif		/* WALPROHIBIT_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 18bc8a7b904..63459305383 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -94,12 +94,37 @@ extern PGDLLIMPORT volatile uint32 CritSectionCount;
 /* in tcop/postgres.c */
 extern void ProcessInterrupts(void);
 
+#ifdef USE_ASSERT_CHECKING
+typedef enum
+{
+	WALPERMIT_UNCHECKED,
+	WALPERMIT_CHECKED,
+	WALPERMIT_CHECKED_AND_USED
+} WALPermitCheckState;
+
+/* in access/walprohibit.c */
+extern WALPermitCheckState walpermit_checked_state;
+
+/*
+ * Reset walpermit_checked flag when no longer in the critical section.
+ * Otherwise, marked checked and used.
+ */
+#define RESET_WALPERMIT_CHECKED_STATE() \
+do { \
+	walpermit_checked_state = CritSectionCount ? \
+	WALPERMIT_CHECKED_AND_USED : WALPERMIT_UNCHECKED; \
+} while(0)
+#else
+#define RESET_WALPERMIT_CHECKED_STATE() ((void) 0)
+#endif
+
 #ifndef WIN32
 
 #define CHECK_FOR_INTERRUPTS() \
 do { \
 	if (unlikely(InterruptPending)) \
 		ProcessInterrupts(); \
+	RESET_WALPERMIT_CHECKED_STATE(); \
 } while(0)
 #else							/* WIN32 */
 
@@ -109,6 +134,7 @@ do { \
 		pgwin32_dispatch_queued_signals(); \
 	if (unlikely(InterruptPending)) \
 		ProcessInterrupts(); \
+	RESET_WALPERMIT_CHECKED_STATE(); \
 } while(0)
 #endif							/* WIN32 */
 
@@ -135,6 +161,7 @@ do { \
 do { \
 	Assert(CritSectionCount > 0); \
 	CritSectionCount--; \
+	RESET_WALPERMIT_CHECKED_STATE(); \
 } while(0)
 
 
-- 
2.22.0

