From 3ddcc088e4f8ba0f0f2abc080f263707bd46e4b1 Mon Sep 17 00:00:00 2001
From: Amul Sul <amul.sul@enterprisedb.com>
Date: Mon, 27 Jul 2020 02:13:36 -0400
Subject: [PATCH v45 4/6] Error or Assert before START_CRIT_SECTION for WAL
 write

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

 - Add 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().
 - Add 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.
---
 contrib/pg_surgery/heap_surgery.c         | 10 ++++--
 src/backend/access/brin/brin.c            |  4 +++
 src/backend/access/brin/brin_pageops.c    | 21 +++++++++--
 src/backend/access/brin/brin_revmap.c     | 10 +++++-
 src/backend/access/gin/ginbtree.c         | 15 ++++++--
 src/backend/access/gin/gindatapage.c      | 18 ++++++++--
 src/backend/access/gin/ginfast.c          | 11 ++++--
 src/backend/access/gin/gininsert.c        |  4 +++
 src/backend/access/gin/ginutil.c          |  9 ++++-
 src/backend/access/gin/ginvacuum.c        | 11 +++++-
 src/backend/access/gist/gist.c            | 25 ++++++++++---
 src/backend/access/gist/gistvacuum.c      | 13 +++++--
 src/backend/access/hash/hash.c            | 19 ++++++++--
 src/backend/access/hash/hashinsert.c      |  9 ++++-
 src/backend/access/hash/hashovfl.c        | 22 +++++++++---
 src/backend/access/hash/hashpage.c        |  9 +++++
 src/backend/access/heap/heapam.c          | 26 +++++++++++++-
 src/backend/access/heap/pruneheap.c       | 16 ++++++---
 src/backend/access/heap/vacuumlazy.c      | 22 +++++++++---
 src/backend/access/heap/visibilitymap.c   | 19 ++++++++--
 src/backend/access/nbtree/nbtdedup.c      |  3 ++
 src/backend/access/nbtree/nbtinsert.c     | 24 ++++++++++---
 src/backend/access/nbtree/nbtpage.c       | 34 +++++++++++++++---
 src/backend/access/spgist/spgdoinsert.c   | 13 +++++++
 src/backend/access/spgist/spgvacuum.c     | 22 ++++++++++--
 src/backend/access/transam/multixact.c    |  5 ++-
 src/backend/access/transam/twophase.c     |  9 +++++
 src/backend/access/transam/varsup.c       |  4 +++
 src/backend/access/transam/walprohibit.c  | 10 ++++++
 src/backend/access/transam/xact.c         |  6 ++++
 src/backend/access/transam/xlog.c         | 30 +++++++++++-----
 src/backend/access/transam/xloginsert.c   | 21 +++++++++--
 src/backend/commands/dbcommands.c         |  3 ++
 src/backend/commands/sequence.c           | 26 +++++++++++---
 src/backend/postmaster/checkpointer.c     |  4 +++
 src/backend/storage/buffer/bufmgr.c       | 13 ++++---
 src/backend/storage/freespace/freespace.c | 10 +++++-
 src/backend/utils/cache/relmapper.c       |  3 ++
 src/include/access/walprohibit.h          | 44 +++++++++++++++++++++++
 src/include/miscadmin.h                   | 26 ++++++++++++++
 40 files changed, 528 insertions(+), 75 deletions(-)

diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c
index 3e641aa644..9952f01a71 100644
--- a/contrib/pg_surgery/heap_surgery.c
+++ b/contrib/pg_surgery/heap_surgery.c
@@ -14,6 +14,7 @@
 
 #include "access/heapam.h"
 #include "access/visibilitymap.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "catalog/pg_am_d.h"
 #include "catalog/pg_proc_d.h"
@@ -89,6 +90,7 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 	OffsetNumber curr_start_ptr,
 				next_start_ptr;
 	bool		include_this_tid[MaxHeapTuplesPerPage];
+	bool		needwal;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -100,6 +102,7 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 	sanity_check_tid_array(ta, &ntids);
 
 	rel = relation_open(relid, RowExclusiveLock);
+	needwal = RelationNeedsWAL(rel);
 
 	/*
 	 * Check target relation.
@@ -235,6 +238,9 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 		if (heap_force_opt == HEAP_FORCE_KILL && PageIsAllVisible(page))
 			visibilitymap_pin(rel, blkno, &vmbuf);
 
+		if (needwal)
+			CheckWALPermitted();
+
 		/* No ereport(ERROR) from here until all the changes are logged. */
 		START_CRIT_SECTION();
 
@@ -315,12 +321,12 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 			MarkBufferDirty(buf);
 
 			/* XLOG stuff */
-			if (RelationNeedsWAL(rel))
+			if (needwal)
 				log_newpage_buffer(buf, true);
 		}
 
 		/* WAL log the VM page if it was modified. */
-		if (did_modify_vm && RelationNeedsWAL(rel))
+		if (did_modify_vm && needwal)
 			log_newpage_buffer(vmbuf, false);
 
 		END_CRIT_SECTION();
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 4366010768..c725db5a32 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"
@@ -901,6 +902,9 @@ brinbuildempty(Relation index)
 		ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
 	LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
 
+	/* Index building transactions will always have a valid XID */
+	AssertWALPermittedHaveXID();
+
 	/* 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 17257919db..3c8443d3c2 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"
@@ -65,6 +66,7 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
 	Buffer		newbuf;
 	BlockNumber newblk = InvalidBlockNumber;
 	bool		extended;
+	bool		needwal = RelationNeedsWAL(idxrel);
 
 	Assert(newsz == MAXALIGN(newsz));
 
@@ -176,13 +178,16 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
 	if (((BrinPageFlags(oldpage) & BRIN_EVACUATE_PAGE) == 0) &&
 		brin_can_do_samepage_update(oldbuf, origsz, newsz))
 	{
+		if (needwal)
+			CheckWALPermitted();
+
 		START_CRIT_SECTION();
 		if (!PageIndexTupleOverwrite(oldpage, oldoff, (Item) unconstify(BrinTuple *, newtup), newsz))
 			elog(ERROR, "failed to replace BRIN tuple");
 		MarkBufferDirty(oldbuf);
 
 		/* XLOG stuff */
-		if (RelationNeedsWAL(idxrel))
+		if (needwal)
 		{
 			xl_brin_samepage_update xlrec;
 			XLogRecPtr	recptr;
@@ -240,6 +245,9 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
 
 		revmapbuf = brinLockRevmapPageForUpdate(revmap, heapBlk);
 
+		if (needwal)
+			CheckWALPermitted();
+
 		START_CRIT_SECTION();
 
 		/*
@@ -267,7 +275,7 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
 		MarkBufferDirty(revmapbuf);
 
 		/* XLOG stuff */
-		if (RelationNeedsWAL(idxrel))
+		if (needwal)
 		{
 			xl_brin_update xlrec;
 			XLogRecPtr	recptr;
@@ -351,6 +359,7 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
 	Buffer		revmapbuf;
 	ItemPointerData tid;
 	bool		extended;
+	bool		needwal;
 
 	Assert(itemsz == MAXALIGN(itemsz));
 
@@ -405,6 +414,10 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
 	page = BufferGetPage(*buffer);
 	blk = BufferGetBlockNumber(*buffer);
 
+	needwal = RelationNeedsWAL(idxrel);
+	if (needwal)
+		CheckWALPermitted();
+
 	/* Execute the actual insertion */
 	START_CRIT_SECTION();
 	if (extended)
@@ -424,7 +437,7 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
 	MarkBufferDirty(revmapbuf);
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(idxrel))
+	if (needwal)
 	{
 		xl_brin_insert xlrec;
 		XLogRecPtr	recptr;
@@ -881,6 +894,8 @@ brin_initialize_empty_new_buffer(Relation idxrel, Buffer buffer)
 			   "brin_initialize_empty_new_buffer: initializing blank page %u",
 			   BufferGetBlockNumber(buffer)));
 
+	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 6e392a551a..4dbc27ca9e 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"
@@ -340,6 +341,7 @@ brinRevmapDesummarizeRange(Relation idxrel, BlockNumber heapBlk)
 	OffsetNumber revmapOffset;
 	OffsetNumber regOffset;
 	ItemId		lp;
+	bool		needwal;
 
 	revmap = brinRevmapInitialize(idxrel, &pagesPerRange, NULL);
 
@@ -405,6 +407,10 @@ brinRevmapDesummarizeRange(Relation idxrel, BlockNumber heapBlk)
 	 * crashed or aborted summarization; remove them silently.
 	 */
 
+	needwal = RelationNeedsWAL(idxrel);
+	if (needwal)
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	ItemPointerSetInvalid(&invalidIptr);
@@ -416,7 +422,7 @@ brinRevmapDesummarizeRange(Relation idxrel, BlockNumber heapBlk)
 	MarkBufferDirty(regBuf);
 	MarkBufferDirty(revmapBuf);
 
-	if (RelationNeedsWAL(idxrel))
+	if (needwal)
 	{
 		xl_brin_desummarize xlrec;
 		XLogRecPtr	recptr;
@@ -614,6 +620,8 @@ revmap_physical_extend(BrinRevmap *revmap)
 		return;
 	}
 
+	AssertWALPermittedHaveXID();
+
 	/*
 	 * 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 8df45478f1..13ee954cc0 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"
@@ -332,6 +333,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 {
 	Page		page = BufferGetPage(stack->buffer);
 	bool		result;
+	bool		needwal;
 	GinPlaceToPageRC rc;
 	uint16		xlflags = 0;
 	Page		childpage = NULL;
@@ -377,6 +379,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 								 insertdata, updateblkno,
 								 &ptp_workspace,
 								 &newlpage, &newrpage);
+	needwal = RelationNeedsWAL(btree->index) && !btree->isBuild;
 
 	if (rc == GPTP_NO_WORK)
 	{
@@ -385,10 +388,13 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 	}
 	else if (rc == GPTP_INSERT)
 	{
+		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);
@@ -409,7 +415,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 			MarkBufferDirty(childbuf);
 		}
 
-		if (RelationNeedsWAL(btree->index) && !btree->isBuild)
+		if (needwal)
 		{
 			XLogRecPtr	recptr;
 			ginxlogInsert xlrec;
@@ -547,6 +553,9 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 			}
 		}
 
+		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
@@ -587,7 +596,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 7c76d1f90d..c0d9fe3069 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"
@@ -811,6 +812,7 @@ ginVacuumPostingTreeLeaf(Relation indexrel, Buffer buffer, GinVacuumState *gvs)
 	if (removedsomething)
 	{
 		bool		modified;
+		bool		needwal;
 
 		/*
 		 * Make sure we have a palloc'd copy of all segments, after the first
@@ -835,8 +837,12 @@ ginVacuumPostingTreeLeaf(Relation indexrel, Buffer buffer, GinVacuumState *gvs)
 			}
 		}
 
-		if (RelationNeedsWAL(indexrel))
+		needwal = RelationNeedsWAL(indexrel);
+		if (needwal)
+		{
 			computeLeafRecompressWALData(leaf);
+			CheckWALPermitted();
+		}
 
 		/* Apply changes to page */
 		START_CRIT_SECTION();
@@ -845,7 +851,7 @@ ginVacuumPostingTreeLeaf(Relation indexrel, Buffer buffer, GinVacuumState *gvs)
 
 		MarkBufferDirty(buffer);
 
-		if (RelationNeedsWAL(indexrel))
+		if (needwal)
 		{
 			XLogRecPtr	recptr;
 
@@ -1777,6 +1783,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 +1832,17 @@ createPostingTree(Relation index, ItemPointerData *items, uint32 nitems,
 	 */
 	PredicateLockPageSplit(index, BufferGetBlockNumber(entrybuffer), blkno);
 
+	needwal = RelationNeedsWAL(index) && !is_build;
+
+	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 7409fdc165..6cb788ff8d 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,8 @@ writeListPage(Relation index, Buffer buffer,
 	PGAlignedBlock workspace;
 	char	   *ptr;
 
+	AssertWALPermittedHaveXID();
+
 	START_CRIT_SECTION();
 
 	GinInitBuffer(buffer, GIN_LIST);
@@ -548,6 +551,7 @@ shiftList(Relation index, Buffer metabuffer, BlockNumber newHead,
 	Page		metapage;
 	GinMetaPageData *metadata;
 	BlockNumber blknoToDelete;
+	bool		needwal = RelationNeedsWAL(index);
 
 	metapage = BufferGetPage(metabuffer);
 	metadata = GinPageGetMeta(metapage);
@@ -586,8 +590,11 @@ shiftList(Relation index, Buffer metabuffer, BlockNumber newHead,
 		 * prepare the XLogInsert machinery for that before entering the
 		 * critical section.
 		 */
-		if (RelationNeedsWAL(index))
+		if (needwal)
+		{
 			XLogEnsureRecordSpace(data.ndeleted, 0);
+			CheckWALPermitted();
+		}
 
 		START_CRIT_SECTION();
 
@@ -625,7 +632,7 @@ shiftList(Relation index, Buffer metabuffer, BlockNumber newHead,
 			MarkBufferDirty(buffers[i]);
 		}
 
-		if (RelationNeedsWAL(index))
+		if (needwal)
 		{
 			XLogRecPtr	recptr;
 
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index ea1c4184fb..35cdef3201 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);
 
+	/* Index building transactions will always have a valid XID */
+	AssertWALPermittedHaveXID();
+
 	/* 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 3d15701a01..a652509ca5 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"
@@ -659,12 +660,18 @@ 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;
+
+	if (needwal)
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	metadata->nTotalPages = stats->nTotalPages;
@@ -684,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 b4fa5f6bf8..4299ed955a 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"
@@ -136,6 +137,7 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
 	Page		page,
 				parentPage;
 	BlockNumber rightlink;
+	bool		needwal;
 
 	/*
 	 * This function MUST be called only if someone of parent pages hold
@@ -159,6 +161,10 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
 	 */
 	PredicateLockPageCombine(gvs->index, deleteBlkno, rightlink);
 
+	needwal = RelationNeedsWAL(gvs->index);
+	if (needwal)
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/* Unlink the page by changing left sibling's rightlink */
@@ -195,7 +201,7 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
 	MarkBufferDirty(lBuffer);
 	MarkBufferDirty(dBuffer);
 
-	if (RelationNeedsWAL(gvs->index))
+	if (needwal)
 	{
 		XLogRecPtr	recptr;
 		ginxlogDeletePage data;
@@ -651,6 +657,9 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 		if (resPage)
 		{
+			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 8c6c744ab7..3982089d68 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 "access/xloginsert.h"
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
@@ -137,6 +138,9 @@ gistbuildempty(Relation index)
 	buffer = ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 
+	/* Index building transactions will always have a valid XID */
+	AssertWALPermittedHaveXID();
+
 	/* Initialize and xlog buffer */
 	START_CRIT_SECTION();
 	GISTInitBuffer(buffer, F_LEAF);
@@ -237,6 +241,7 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
 	XLogRecPtr	recptr;
 	int			i;
 	bool		is_split;
+	bool		needwal = RelationNeedsWAL(rel);
 
 	/*
 	 * Refuse to modify a page that's incompletely split. This should not
@@ -471,8 +476,11 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
 		 * insertion for that. NB: The number of pages and data segments
 		 * specified here must match the calculations in gistXLogSplit()!
 		 */
-		if (!is_build && RelationNeedsWAL(rel))
+		if (!is_build && needwal)
+		{
 			XLogEnsureRecordSpace(npage, 1 + npage * 2);
+			CheckWALPermitted();
+		}
 
 		START_CRIT_SECTION();
 
@@ -506,7 +514,7 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
 			recptr = GistBuildLSN;
 		else
 		{
-			if (RelationNeedsWAL(rel))
+			if (needwal)
 				recptr = gistXLogSplit(is_leaf,
 									   dist, oldrlink, oldnsn, leftchildbuf,
 									   markfollowright);
@@ -532,6 +540,9 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
 	}
 	else
 	{
+		if (!is_build && needwal)
+			CheckWALPermitted();
+
 		/*
 		 * Enough space.  We always get here if ntup==0.
 		 */
@@ -573,7 +584,7 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
 			recptr = GistBuildLSN;
 		else
 		{
-			if (RelationNeedsWAL(rel))
+			if (needwal)
 			{
 				OffsetNumber ndeloffs = 0,
 							deloffs[1];
@@ -1647,6 +1658,7 @@ gistprunepage(Relation rel, Page page, Buffer buffer, Relation heapRel)
 	int			ndeletable = 0;
 	OffsetNumber offnum,
 				maxoff;
+	bool		needwal = RelationNeedsWAL(rel);
 
 	Assert(GistPageIsLeaf(page));
 
@@ -1669,11 +1681,14 @@ gistprunepage(Relation rel, Page page, Buffer buffer, Relation heapRel)
 	{
 		TransactionId latestRemovedXid = InvalidTransactionId;
 
-		if (XLogStandbyInfoActive() && RelationNeedsWAL(rel))
+		if (XLogStandbyInfoActive() && needwal)
 			latestRemovedXid =
 				index_compute_xid_horizon_for_tuples(rel, heapRel, buffer,
 													 deletable, ndeletable);
 
+		if (needwal)
+			CheckWALPermitted();
+
 		START_CRIT_SECTION();
 
 		PageIndexMultiDelete(page, deletable, ndeletable);
@@ -1690,7 +1705,7 @@ gistprunepage(Relation rel, Page page, Buffer buffer, Relation heapRel)
 		MarkBufferDirty(buffer);
 
 		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
+		if (needwal)
 		{
 			XLogRecPtr	recptr;
 
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index f190decdff..ffed0e75e8 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"
@@ -278,6 +279,7 @@ gistvacuumpage(GistVacState *vstate, BlockNumber blkno, BlockNumber orig_blkno)
 	Buffer		buffer;
 	Page		page;
 	BlockNumber recurse_to;
+	bool		needwal = RelationNeedsWAL(rel);
 
 restart:
 	recurse_to = InvalidBlockNumber;
@@ -359,6 +361,9 @@ restart:
 		 */
 		if (ntodelete > 0)
 		{
+			if (needwal)
+				CheckWALPermitted();
+
 			START_CRIT_SECTION();
 
 			MarkBufferDirty(buffer);
@@ -366,7 +371,7 @@ restart:
 			PageIndexMultiDelete(page, todelete, ntodelete);
 			GistMarkTuplesDeleted(page);
 
-			if (RelationNeedsWAL(rel))
+			if (needwal)
 			{
 				XLogRecPtr	recptr;
 
@@ -595,6 +600,7 @@ gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	IndexTuple	idxtuple;
 	XLogRecPtr	recptr;
 	FullTransactionId txid;
+	bool		needwal = RelationNeedsWAL(info->index);
 
 	/*
 	 * Check that the leaf is still empty and deletable.
@@ -649,6 +655,9 @@ gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	 */
 	txid = ReadNextFullTransactionId();
 
+	if (needwal)
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/* mark the page as deleted */
@@ -661,7 +670,7 @@ gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	MarkBufferDirty(parentBuffer);
 	PageIndexTupleDelete(parentPage, downlink);
 
-	if (RelationNeedsWAL(info->index))
+	if (needwal)
 		recptr = gistXLogPageDelete(leafBuffer, txid, parentBuffer, downlink);
 	else
 		recptr = gistGetFakeLSN(info->index);
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index fd1a7119b6..2fa9f45105 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 "access/xloginsert.h"
 #include "catalog/index.h"
 #include "commands/progress.h"
@@ -470,6 +471,7 @@ hashbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	Buffer		metabuf = InvalidBuffer;
 	HashMetaPage metap;
 	HashMetaPage cachedmetap;
+	bool		needwal;
 
 	tuples_removed = 0;
 	num_index_tuples = 0;
@@ -576,6 +578,10 @@ loop_top:
 		goto loop_top;
 	}
 
+	needwal = RelationNeedsWAL(rel);
+	if (needwal)
+		CheckWALPermitted();
+
 	/* Okay, we're really done.  Update tuple count in metapage. */
 	START_CRIT_SECTION();
 
@@ -606,7 +612,7 @@ loop_top:
 	MarkBufferDirty(metabuf);
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(rel))
+	if (needwal)
 	{
 		xl_hash_update_meta_page xlrec;
 		XLogRecPtr	recptr;
@@ -693,6 +699,7 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 	Buffer		buf;
 	Bucket		new_bucket PG_USED_FOR_ASSERTS_ONLY = InvalidBucket;
 	bool		bucket_dirty = false;
+	bool		needwal = RelationNeedsWAL(rel);
 
 	blkno = bucket_blkno;
 	buf = bucket_buf;
@@ -791,6 +798,9 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 		 */
 		if (ndeletable > 0)
 		{
+			if (needwal)
+				CheckWALPermitted();
+
 			/* No ereport(ERROR) until changes are logged */
 			START_CRIT_SECTION();
 
@@ -812,7 +822,7 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 			MarkBufferDirty(buf);
 
 			/* XLOG stuff */
-			if (RelationNeedsWAL(rel))
+			if (needwal)
 			{
 				xl_hash_delete xlrec;
 				XLogRecPtr	recptr;
@@ -886,6 +896,9 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 		page = BufferGetPage(bucket_buf);
 		bucket_opaque = HashPageGetOpaque(page);
 
+		if (needwal)
+			CheckWALPermitted();
+
 		/* No ereport(ERROR) until changes are logged */
 		START_CRIT_SECTION();
 
@@ -893,7 +906,7 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 		MarkBufferDirty(bucket_buf);
 
 		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
+		if (needwal)
 		{
 			XLogRecPtr	recptr;
 
diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 4f2fecb908..e1be1761e8 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 "access/xloginsert.h"
 #include "miscadmin.h"
 #include "storage/buf_internals.h"
@@ -194,6 +195,8 @@ restart_insert:
 	 */
 	LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
 
+	AssertWALPermittedHaveXID();
+
 	/* Do the update.  No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
@@ -361,6 +364,7 @@ _hash_vacuum_one_page(Relation rel, Relation hrel, Buffer metabuf, Buffer buf)
 	if (ndeletable > 0)
 	{
 		TransactionId latestRemovedXid;
+		bool		needwal = RelationNeedsWAL(rel);
 
 		latestRemovedXid =
 			index_compute_xid_horizon_for_tuples(rel, hrel, buf,
@@ -371,6 +375,9 @@ _hash_vacuum_one_page(Relation rel, Relation hrel, Buffer metabuf, Buffer buf)
 		 */
 		LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
 
+		if (needwal)
+			CheckWALPermitted();
+
 		/* No ereport(ERROR) until changes are logged */
 		START_CRIT_SECTION();
 
@@ -394,7 +401,7 @@ _hash_vacuum_one_page(Relation rel, Relation hrel, Buffer metabuf, Buffer buf)
 		MarkBufferDirty(metabuf);
 
 		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
+		if (needwal)
 		{
 			xl_hash_vacuum_one_page xlrec;
 			XLogRecPtr	recptr;
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index e34cfc302d..1cb73610fc 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 "access/xloginsert.h"
 #include "miscadmin.h"
 #include "utils/rel.h"
@@ -313,6 +314,8 @@ _hash_addovflpage(Relation rel, Buffer metabuf, Buffer buf, bool retain_pin)
 
 found:
 
+	AssertWALPermittedHaveXID();
+
 	/*
 	 * 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
@@ -511,6 +514,7 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
 	Buffer		prevbuf = InvalidBuffer;
 	Buffer		nextbuf = InvalidBuffer;
 	bool		update_metap = false;
+	bool		needwal;
 
 	/* Get information from the doomed page */
 	_hash_checkpage(rel, ovflbuf, LH_OVERFLOW_PAGE);
@@ -574,9 +578,14 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
 	/* Get write-lock on metapage to update firstfree */
 	LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
 
+	needwal = RelationNeedsWAL(rel);
+
 	/* This operation needs to log multiple tuples, prepare WAL for that */
-	if (RelationNeedsWAL(rel))
+	if (needwal)
+	{
 		XLogEnsureRecordSpace(HASH_XLOG_FREE_OVFL_BUFS, 4 + nitups);
+		CheckWALPermitted();
+	}
 
 	START_CRIT_SECTION();
 
@@ -642,7 +651,7 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
 	}
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(rel))
+	if (needwal)
 	{
 		xl_hash_squeeze_page xlrec;
 		XLogRecPtr	recptr;
@@ -923,14 +932,19 @@ readpage:
 
 				if (nitups > 0)
 				{
+					bool		needwal = RelationNeedsWAL(rel);
+
 					Assert(nitups == ndeletable);
 
 					/*
 					 * This operation needs to log multiple tuples, prepare
 					 * WAL for that.
 					 */
-					if (RelationNeedsWAL(rel))
+					if (needwal)
+					{
 						XLogEnsureRecordSpace(0, 3 + nitups);
+						CheckWALPermitted();
+					}
 
 					START_CRIT_SECTION();
 
@@ -948,7 +962,7 @@ readpage:
 					MarkBufferDirty(rbuf);
 
 					/* XLOG stuff */
-					if (RelationNeedsWAL(rel))
+					if (needwal)
 					{
 						XLogRecPtr	recptr;
 						xl_hash_move_page_contents xlrec;
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 39206d1942..9521585706 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 "access/xloginsert.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
@@ -817,6 +818,8 @@ restart_expand:
 		goto fail;
 	}
 
+	AssertWALPermittedHaveXID();
+
 	/*
 	 * 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
@@ -1173,6 +1176,8 @@ _hash_splitbucket(Relation rel,
 
 				if (PageGetFreeSpaceForMultipleTuples(npage, nitups + 1) < (all_tups_size + itemsz))
 				{
+					AssertWALPermittedHaveXID();
+
 					/*
 					 * Change the shared buffer state in critical section,
 					 * otherwise any error could make it unrecoverable.
@@ -1224,6 +1229,8 @@ _hash_splitbucket(Relation rel,
 		/* Exit loop if no more overflow pages in old bucket */
 		if (!BlockNumberIsValid(oblkno))
 		{
+			AssertWALPermittedHaveXID();
+
 			/*
 			 * Change the shared buffer state in critical section, otherwise
 			 * any error could make it unrecoverable.
@@ -1270,6 +1277,8 @@ _hash_splitbucket(Relation rel,
 	npage = BufferGetPage(bucket_nbuf);
 	nopaque = HashPageGetOpaque(npage);
 
+	AssertWALPermittedHaveXID();
+
 	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 1ee985f633..d7d8015e87 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -47,6 +47,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"
@@ -2059,6 +2060,8 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	 */
 	CheckForSerializableConflictIn(relation, NULL, InvalidBlockNumber);
 
+	AssertWALPermittedHaveXID();
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -2343,6 +2346,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 		if (starting_with_empty_page && (options & HEAP_INSERT_FROZEN))
 			all_frozen_set = true;
 
+		AssertWALPermittedHaveXID();
+
 		/* NO EREPORT(ERROR) from here till changes are logged */
 		START_CRIT_SECTION();
 
@@ -2903,6 +2908,8 @@ l1:
 							  xid, LockTupleExclusive, true,
 							  &new_xmax, &new_infomask, &new_infomask2);
 
+	AssertWALPermittedHaveXID();
+
 	START_CRIT_SECTION();
 
 	/*
@@ -3652,6 +3659,8 @@ l2:
 
 		Assert(HEAP_XMAX_IS_LOCKED_ONLY(infomask_lock_old_tuple));
 
+		AssertWALPermittedHaveXID();
+
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
@@ -3837,6 +3846,8 @@ l2:
 										   id_has_external,
 										   &old_key_copied);
 
+	AssertWALPermittedHaveXID();
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -4818,6 +4829,8 @@ failed:
 							  GetCurrentTransactionId(), mode, false,
 							  &xid, &new_infomask, &new_infomask2);
 
+	AssertWALPermittedHaveXID();
+
 	START_CRIT_SECTION();
 
 	/*
@@ -5608,6 +5621,8 @@ l4:
 								VISIBILITYMAP_ALL_FROZEN))
 			cleared_all_frozen = true;
 
+		AssertWALPermittedHaveXID();
+
 		START_CRIT_SECTION();
 
 		/* ... and set them */
@@ -5766,6 +5781,8 @@ heap_finish_speculative(Relation relation, ItemPointer tid)
 	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
 					 "invalid speculative token constant");
 
+	AssertWALPermittedHaveXID();
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -5874,6 +5891,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 		elog(ERROR, "attempted to kill a non-speculative tuple");
 	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
 
+	AssertWALPermittedHaveXID();
+
 	/*
 	 * No need to check for serializable conflicts here.  There is never a
 	 * need for a combo CID, either.  No need to extract replica identity, or
@@ -5994,6 +6013,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
 	HeapTupleHeader htup;
 	uint32		oldlen;
 	uint32		newlen;
+	bool		needwal;
 
 	/*
 	 * For now, we don't allow parallel updates.  Unlike a regular update,
@@ -6024,6 +6044,10 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
 	if (oldlen != newlen || htup->t_hoff != tuple->t_data->t_hoff)
 		elog(ERROR, "wrong tuple length");
 
+	needwal = RelationNeedsWAL(relation);
+	if (needwal)
+		CheckWALPermitted();
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -6034,7 +6058,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
 	MarkBufferDirty(buffer);
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(relation))
+	if (needwal)
 	{
 		xl_heap_inplace xlrec;
 		XLogRecPtr	recptr;
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 98d31de003..cfa3976717 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 "access/xloginsert.h"
 #include "catalog/catalog.h"
@@ -115,11 +116,11 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
 	Size		minfree;
 
 	/*
-	 * We can't write WAL in recovery 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.
+	 * We can't write if WAL is prohibited or recovery is in progress, 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;
 
 	/*
@@ -277,6 +278,7 @@ heap_page_prune(Relation relation, Buffer buffer,
 				maxoff;
 	PruneState	prstate;
 	HeapTupleData tup;
+	bool		needwal;
 
 	/*
 	 * Our strategy is to scan the page and make lists of items to change,
@@ -380,6 +382,10 @@ heap_page_prune(Relation relation, Buffer buffer,
 	if (off_loc)
 		*off_loc = InvalidOffsetNumber;
 
+	needwal = RelationNeedsWAL(relation);
+	if (needwal)
+		CheckWALPermitted();
+
 	/* Any error while applying the changes is critical */
 	START_CRIT_SECTION();
 
@@ -413,7 +419,7 @@ heap_page_prune(Relation relation, Buffer buffer,
 		/*
 		 * Emit a WAL XLOG_HEAP2_PRUNE record showing what we did
 		 */
-		if (RelationNeedsWAL(relation))
+		if (needwal)
 		{
 			xl_heap_prune xlrec;
 			XLogRecPtr	recptr;
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index e1cac74e62..ca6bf27539 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -42,6 +42,7 @@
 #include "access/multixact.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
@@ -1480,6 +1481,11 @@ lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno,
 		 */
 		if (!PageIsAllVisible(page))
 		{
+			bool 		needwal = RelationNeedsWAL(vacrel->rel);
+
+			if (needwal)
+				CheckWALPermitted();
+
 			START_CRIT_SECTION();
 
 			/* mark buffer dirty before writing a WAL record */
@@ -1494,8 +1500,7 @@ lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno,
 			 * To prevent that, check whether the page has been previously
 			 * WAL-logged, and if not, do that now.
 			 */
-			if (RelationNeedsWAL(vacrel->rel) &&
-				PageGetLSN(page) == InvalidXLogRecPtr)
+			if (needwal && PageGetLSN(page) == InvalidXLogRecPtr)
 				log_newpage_buffer(buf, true);
 
 			PageSetAllVisible(page);
@@ -1811,8 +1816,13 @@ retry:
 	 */
 	if (nfrozen > 0)
 	{
+		bool		needwal = RelationNeedsWAL(vacrel->rel);
+
 		Assert(prunestate->hastup);
 
+		if (needwal)
+			CheckWALPermitted();
+
 		/*
 		 * At least one tuple with storage needs to be frozen -- execute that
 		 * now.
@@ -1837,7 +1847,7 @@ retry:
 		}
 
 		/* Now WAL-log freezing if necessary */
-		if (RelationNeedsWAL(vacrel->rel))
+		if (needwal)
 		{
 			XLogRecPtr	recptr;
 
@@ -2497,6 +2507,7 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
 	TransactionId visibility_cutoff_xid;
 	bool		all_frozen;
 	LVSavedErrInfo saved_err_info;
+	bool		needwal = RelationNeedsWAL(vacrel->rel);
 
 	Assert(vacrel->nindexes == 0 || vacrel->do_index_vacuuming);
 
@@ -2507,6 +2518,9 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
 							 VACUUM_ERRCB_PHASE_VACUUM_HEAP, blkno,
 							 InvalidOffsetNumber);
 
+	if (needwal)
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	for (; index < dead_items->num_items; index++)
@@ -2537,7 +2551,7 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
 	MarkBufferDirty(buffer);
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(vacrel->rel))
+	if (needwal)
 	{
 		xl_heap_vacuum xlrec;
 		XLogRecPtr	recptr;
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index e09f25a684..1bfe0a5fb3 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/xloginsert.h"
 #include "access/xlogutils.h"
 #include "miscadmin.h"
@@ -250,6 +251,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf,
 	uint8		mapOffset = HEAPBLK_TO_OFFSET(heapBlk);
 	Page		page;
 	uint8	   *map;
+	bool		needwal = RelationNeedsWAL(rel);
 
 #ifdef TRACE_VISIBILITYMAP
 	elog(DEBUG1, "vm_set %s %d", RelationGetRelationName(rel), heapBlk);
@@ -273,12 +275,19 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf,
 
 	if (flags != (map[mapByte] >> mapOffset & VISIBILITYMAP_VALID_BITS))
 	{
+		/*
+		 * Can reach here from VACUUM or from startup process, so need not have an
+		 * XID.
+		 */
+		if (needwal && XLogRecPtrIsInvalid(recptr))
+			CheckWALPermitted();
+
 		START_CRIT_SECTION();
 
 		map[mapByte] |= (flags << mapOffset);
 		MarkBufferDirty(vmBuf);
 
-		if (RelationNeedsWAL(rel))
+		if (needwal)
 		{
 			if (XLogRecPtrIsInvalid(recptr))
 			{
@@ -475,6 +484,7 @@ visibilitymap_prepare_truncate(Relation rel, BlockNumber nheapblocks)
 		Buffer		mapBuffer;
 		Page		page;
 		char	   *map;
+		bool		needwal;
 
 		newnblocks = truncBlock + 1;
 
@@ -488,8 +498,13 @@ visibilitymap_prepare_truncate(Relation rel, BlockNumber nheapblocks)
 		page = BufferGetPage(mapBuffer);
 		map = PageGetContents(page);
 
+		needwal = (!InRecovery && RelationNeedsWAL(rel) && XLogHintBitIsNeeded());
+
 		LockBuffer(mapBuffer, BUFFER_LOCK_EXCLUSIVE);
 
+		if (needwal)
+			CheckWALPermitted();
+
 		/* NO EREPORT(ERROR) from here till changes are logged */
 		START_CRIT_SECTION();
 
@@ -517,7 +532,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 0207421a5d..7237f985bb 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 "access/xloginsert.h"
 #include "miscadmin.h"
 #include "utils/rel.h"
@@ -236,6 +237,8 @@ _bt_dedup_pass(Relation rel, Buffer buf, Relation heapRel, IndexTuple newitem,
 		nopaque->btpo_flags &= ~BTP_HAS_GARBAGE;
 	}
 
+	AssertWALPermittedHaveXID();
+
 	START_CRIT_SECTION();
 
 	PageRestoreTempPage(newpage, page);
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index f6f4af8bfe..1530a6f371 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -18,6 +18,7 @@
 #include "access/nbtree.h"
 #include "access/nbtxlog.h"
 #include "access/transam.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "common/pg_prng.h"
 #include "lib/qunique.h"
@@ -1241,6 +1242,7 @@ _bt_insertonpg(Relation rel,
 		Page		metapg = NULL;
 		BTMetaPageData *metad = NULL;
 		BlockNumber blockcache;
+		bool		needwal = RelationNeedsWAL(rel);
 
 		/*
 		 * If we are doing this insert because we split a page that was the
@@ -1266,6 +1268,9 @@ _bt_insertonpg(Relation rel,
 			}
 		}
 
+		if (needwal)
+			CheckWALPermitted();
+
 		/* Do the update.  No ereport(ERROR) until changes are logged */
 		START_CRIT_SECTION();
 
@@ -1304,7 +1309,7 @@ _bt_insertonpg(Relation rel,
 		}
 
 		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
+		if (needwal)
 		{
 			xl_btree_insert xlrec;
 			xl_btree_metadata xlmeta;
@@ -1489,6 +1494,7 @@ _bt_split(Relation rel, BTScanInsert itup_key, Buffer buf, Buffer cbuf,
 	bool		newitemonleft,
 				isleaf,
 				isrightmost;
+	bool		needwal;
 
 	/*
 	 * origpage is the original page to be split.  leftpage is a temporary
@@ -1916,13 +1922,18 @@ _bt_split(Relation rel, BTScanInsert itup_key, Buffer buf, Buffer cbuf,
 			ropaque->btpo_flags |= BTP_SPLIT_END;
 	}
 
+	needwal = RelationNeedsWAL(rel);
+	if (needwal)
+		CheckWALPermitted();
+
 	/*
 	 * 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();
 
@@ -1959,7 +1970,7 @@ _bt_split(Relation rel, BTScanInsert itup_key, Buffer buf, Buffer cbuf,
 	}
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(rel))
+	if (needwal)
 	{
 		xl_btree_split xlrec;
 		uint8		xlinfo;
@@ -2447,6 +2458,7 @@ _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf)
 	Buffer		metabuf;
 	Page		metapg;
 	BTMetaPageData *metad;
+	bool		needwal;
 
 	lbkno = BufferGetBlockNumber(lbuf);
 	rbkno = BufferGetBlockNumber(rbuf);
@@ -2484,6 +2496,10 @@ _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf)
 	right_item = CopyIndexTuple(item);
 	BTreeTupleSetDownLink(right_item, rbkno);
 
+	needwal = RelationNeedsWAL(rel);
+	if (needwal)
+		CheckWALPermitted();
+
 	/* NO EREPORT(ERROR) from here till newroot op is logged */
 	START_CRIT_SECTION();
 
@@ -2541,7 +2557,7 @@ _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf)
 	MarkBufferDirty(metabuf);
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(rel))
+	if (needwal)
 	{
 		xl_btree_newroot xlrec;
 		XLogRecPtr	recptr;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 20adb602a4..1f0c476208 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"
@@ -236,6 +237,7 @@ _bt_set_cleanup_info(Relation rel, BlockNumber num_delpages)
 	Buffer		metabuf;
 	Page		metapg;
 	BTMetaPageData *metad;
+	bool		needwal;
 
 	/*
 	 * On-disk compatibility note: The btm_last_cleanup_num_delpages metapage
@@ -272,6 +274,10 @@ _bt_set_cleanup_info(Relation rel, BlockNumber num_delpages)
 	_bt_unlockbuf(rel, metabuf);
 	_bt_lockbuf(rel, metabuf, BT_WRITE);
 
+	needwal = RelationNeedsWAL(rel);
+	if (needwal)
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/* upgrade meta-page if needed */
@@ -284,7 +290,7 @@ _bt_set_cleanup_info(Relation rel, BlockNumber num_delpages)
 	MarkBufferDirty(metabuf);
 
 	/* write wal record if needed */
-	if (RelationNeedsWAL(rel))
+	if (needwal)
 	{
 		xl_btree_metadata md;
 		XLogRecPtr	recptr;
@@ -403,6 +409,7 @@ _bt_getroot(Relation rel, int access)
 	if (metad->btm_root == P_NONE)
 	{
 		Page		metapg;
+		bool		needwal;
 
 		/* If access = BT_READ, caller doesn't want us to create root yet */
 		if (access == BT_READ)
@@ -448,6 +455,10 @@ _bt_getroot(Relation rel, int access)
 		/* Get raw page pointer for metapage */
 		metapg = BufferGetPage(metabuf);
 
+		needwal = RelationNeedsWAL(rel);
+		if (needwal)
+			CheckWALPermitted();
+
 		/* NO ELOG(ERROR) till meta is updated */
 		START_CRIT_SECTION();
 
@@ -466,7 +477,7 @@ _bt_getroot(Relation rel, int access)
 		MarkBufferDirty(metabuf);
 
 		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
+		if (needwal)
 		{
 			xl_btree_newroot xlrec;
 			XLogRecPtr	recptr;
@@ -1183,6 +1194,9 @@ _bt_delitems_vacuum(Relation rel, Buffer buf,
 										 updatedoffsets, &updatedbuflen,
 										 needswal);
 
+	if (needswal)
+		CheckWALPermitted();
+
 	/* No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
@@ -1313,6 +1327,8 @@ _bt_delitems_delete(Relation rel, Buffer buf, TransactionId latestRemovedXid,
 										 updatedoffsets, &updatedbuflen,
 										 needswal);
 
+	AssertWALPermittedHaveXID();
+
 	/* No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
@@ -2097,6 +2113,7 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
 	OffsetNumber nextoffset;
 	IndexTuple	itup;
 	IndexTupleData trunctuple;
+	bool		needwal;
 
 	page = BufferGetPage(leafbuf);
 	opaque = BTPageGetOpaque(page);
@@ -2185,6 +2202,10 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
 	 */
 	PredicateLockPageCombine(rel, leafblkno, leafrightsib);
 
+	needwal = RelationNeedsWAL(rel);
+	if (needwal)
+		CheckWALPermitted();
+
 	/* No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
@@ -2236,7 +2257,7 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
 	MarkBufferDirty(leafbuf);
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(rel))
+	if (needwal)
 	{
 		xl_btree_mark_page_halfdead xlrec;
 		XLogRecPtr	recptr;
@@ -2323,6 +2344,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 	uint32		targetlevel;
 	IndexTuple	leafhikey;
 	BlockNumber leaftopparent;
+	bool		needwal;
 
 	page = BufferGetPage(leafbuf);
 	opaque = BTPageGetOpaque(page);
@@ -2554,6 +2576,10 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 	 * Here we begin doing the deletion.
 	 */
 
+	needwal = RelationNeedsWAL(rel);
+	if (needwal)
+		CheckWALPermitted();
+
 	/* No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
@@ -2630,7 +2656,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 		MarkBufferDirty(leafbuf);
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(rel))
+	if (needwal)
 	{
 		xl_btree_unlink_page xlrec;
 		xl_btree_metadata xlmeta;
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index e84b5edc03..6d3adcd0e3 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 "common/pg_prng.h"
 #include "miscadmin.h"
@@ -215,6 +216,8 @@ addLeafTuple(Relation index, SpGistState *state, SpGistLeafTuple leafTuple,
 	xlrec.offnumParent = InvalidOffsetNumber;
 	xlrec.nodeI = 0;
 
+	AssertWALPermittedHaveXID();
+
 	START_CRIT_SECTION();
 
 	if (current->offnum == InvalidOffsetNumber ||
@@ -459,6 +462,8 @@ moveLeafs(Relation index, SpGistState *state,
 
 	leafdata = leafptr = palloc(size);
 
+	AssertWALPermittedHaveXID();
+
 	START_CRIT_SECTION();
 
 	/* copy all the old tuples to new page, unless they're dead */
@@ -1132,6 +1137,8 @@ doPickSplit(Relation index, SpGistState *state,
 
 	leafdata = leafptr = (char *) palloc(totalLeafSizes);
 
+	AssertWALPermittedHaveXID();
+
 	/* Here we begin making the changes to the target pages */
 	START_CRIT_SECTION();
 
@@ -1540,6 +1547,8 @@ spgAddNodeAction(Relation index, SpGistState *state,
 	if (PageGetExactFreeSpace(current->page) >=
 		newInnerTuple->size - innerTuple->size)
 	{
+		AssertWALPermittedHaveXID();
+
 		/*
 		 * We can replace the inner tuple by new version in-place
 		 */
@@ -1626,6 +1635,8 @@ spgAddNodeAction(Relation index, SpGistState *state,
 		else
 			xlrec.parentBlk = 2;
 
+		AssertWALPermittedHaveXID();
+
 		START_CRIT_SECTION();
 
 		/* insert new ... */
@@ -1811,6 +1822,8 @@ spgSplitNodeAction(Relation index, SpGistState *state,
 									&xlrec.newPage);
 	}
 
+	AssertWALPermittedHaveXID();
+
 	START_CRIT_SECTION();
 
 	/*
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 0049630532..acd6c89270 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"
@@ -139,6 +140,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 	int			nDeletable;
 	OffsetNumber i,
 				max = PageGetMaxOffsetNumber(page);
+	bool		needwal;
 
 	memset(predecessor, 0, sizeof(predecessor));
 	memset(deletable, 0, sizeof(deletable));
@@ -323,6 +325,10 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 	if (nDeletable != xlrec.nDead + xlrec.nPlaceholder + xlrec.nMove)
 		elog(ERROR, "inconsistent counts of deletable tuples");
 
+	needwal = RelationNeedsWAL(index);
+	if (needwal)
+		CheckWALPermitted();
+
 	/* Do the updates */
 	START_CRIT_SECTION();
 
@@ -371,7 +377,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 
 	MarkBufferDirty(buffer);
 
-	if (RelationNeedsWAL(index))
+	if (needwal)
 	{
 		XLogRecPtr	recptr;
 
@@ -411,6 +417,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 	OffsetNumber toDelete[MaxIndexTuplesPerPage];
 	OffsetNumber i,
 				max = PageGetMaxOffsetNumber(page);
+	bool		needwal;
 
 	xlrec.nDelete = 0;
 
@@ -447,6 +454,10 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 	if (xlrec.nDelete == 0)
 		return;					/* nothing more to do */
 
+	needwal = RelationNeedsWAL(index);
+	if (needwal)
+		CheckWALPermitted();
+
 	/* Do the update */
 	START_CRIT_SECTION();
 
@@ -455,7 +466,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 
 	MarkBufferDirty(buffer);
 
-	if (RelationNeedsWAL(index))
+	if (needwal)
 	{
 		XLogRecPtr	recptr;
 
@@ -502,6 +513,7 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer)
 	OffsetNumber itemnos[MaxIndexTuplesPerPage];
 	spgxlogVacuumRedirect xlrec;
 	GlobalVisState *vistest;
+	bool		needwal;
 
 	xlrec.nToPlaceholder = 0;
 	xlrec.newestRedirectXid = InvalidTransactionId;
@@ -509,6 +521,10 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer)
 	/* XXX: providing heap relation would allow more pruning */
 	vistest = GlobalVisTestFor(NULL);
 
+	needwal = RelationNeedsWAL(index);
+	if (needwal)
+		CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/*
@@ -584,7 +600,7 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer)
 	if (hasUpdate)
 		MarkBufferDirty(buffer);
 
-	if (hasUpdate && RelationNeedsWAL(index))
+	if (hasUpdate && needwal)
 	{
 		XLogRecPtr	recptr;
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 9f65c600d0..f05082fccc 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/xloginsert.h"
 #include "access/xlogutils.h"
@@ -1174,6 +1175,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 
 	ExtendMultiXactMember(nextOffset, nmembers);
 
+	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
@@ -2964,7 +2967,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 b35da6f1aa..0a07d5cf7a 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"
@@ -1184,6 +1185,8 @@ EndPrepare(GlobalTransaction gxact)
 	 */
 	XLogEnsureRecordSpace(0, records.num_chunks);
 
+	AssertWALPermittedHaveXID();
+
 	START_CRIT_SECTION();
 
 	Assert((MyProc->delayChkpt & DELAY_CHKPT_START) == 0);
@@ -2305,6 +2308,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 */
@@ -2407,6 +2413,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 748120a012..86ccbc3c82 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/xlogutils.h"
 #include "commands/dbcommands.h"
@@ -75,6 +76,9 @@ GetNewTransactionId(bool isSubXact)
 	if (RecoveryInProgress())
 		elog(ERROR, "cannot assign TransactionIds during recovery");
 
+	/* do not assign transaction id when WAL is prohibited */
+	CheckWALPermitted();
+
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 
 	full_xid = ShmemVariableCache->nextXid;
diff --git a/src/backend/access/transam/walprohibit.c b/src/backend/access/transam/walprohibit.c
index d968c71494..b0e527ae74 100644
--- a/src/backend/access/transam/walprohibit.c
+++ b/src/backend/access/transam/walprohibit.c
@@ -27,6 +27,16 @@
 #include "utils/fmgroids.h"
 #include "utils/fmgrprotos.h"
 
+/*
+ * Assert flag to enforce WAL insert permission check rule before starting a
+ * critical section for the WAL writes.  For this, either of
+ * CheckWALPermitted(), AssertWALPermittedHaveXID(), or AssertWALPermitted()
+ * must be called before starting the critical section.
+ */
+#ifdef USE_ASSERT_CHECKING
+WALProhibitCheckState walprohibit_checked_state = WALPROHIBIT_UNCHECKED;
+#endif
+
 /*
  * Shared-memory WAL prohibit state structure
  */
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 62d1018220..0c3a207bf6 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"
@@ -1375,6 +1376,8 @@ RecordTransactionCommit(void)
 		/* Tell bufmgr and smgr to prepare for commit */
 		BufmgrCommit();
 
+		AssertWALPermittedHaveXID();
+
 		/*
 		 * Mark ourselves as within our "commit critical section".  This
 		 * forces any concurrent checkpoint to wait until we've updated
@@ -1741,6 +1744,9 @@ RecordTransactionAbort(bool isSubXact)
 		elog(PANIC, "cannot abort transaction %u, it was already committed",
 			 xid);
 
+	/* We'll be reaching here with valid XID only. */
+	AssertWALPermittedHaveXID();
+
 	/* 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 5447dcb085..588df3cb6d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -773,7 +773,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");
 
 	/*
 	 * Given that we're not in recovery, InsertTimeLineID is set and can't
@@ -2542,9 +2542,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;
@@ -6759,6 +6761,9 @@ CreateCheckPoint(int flags)
 	if (!shutdown && XLogStandbyInfoActive())
 		LogStandbySnapshot();
 
+	/* Error out if wal writes are disabled. */
+	CheckWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/*
@@ -6922,6 +6927,9 @@ CreateEndOfRecoveryRecord(void)
 	xlrec.PrevTimeLineID = XLogCtl->PrevTimeLineID;
 	WALInsertLockRelease();
 
+	/* Assured that WAL permission has been checked */
+	AssertWALPermitted();
+
 	START_CRIT_SECTION();
 
 	XLogBeginInsert();
@@ -6997,6 +7005,9 @@ CreateOverwriteContrecordRecord(XLogRecPtr aborted_lsn, XLogRecPtr pagePtr,
 		elog(ERROR, "invalid WAL insert position %X/%X for OVERWRITE_CONTRECORD",
 			 LSN_FORMAT_ARGS(recptr));
 
+	/* Assured that WAL permission has been checked */
+	AssertWALPermitted();
+
 	START_CRIT_SECTION();
 
 	/*
@@ -7665,7 +7676,7 @@ void
 UpdateFullPageWrites(void)
 {
 	XLogCtlInsert *Insert = &XLogCtl->Insert;
-	bool		recoveryInProgress;
+	bool		WALInsertAllowed;
 
 	/*
 	 * Do nothing if full_page_writes has not been changed.
@@ -7679,10 +7690,10 @@ UpdateFullPageWrites(void)
 
 	/*
 	 * Perform this outside critical section so that the WAL insert
-	 * initialization done by RecoveryInProgress() doesn't trigger an
-	 * assertion failure.
+	 * initialization done by XLogInsertAllowed() doesn't trigger an assertion
+	 * failure.
 	 */
-	recoveryInProgress = RecoveryInProgress();
+	WALInsertAllowed = XLogInsertAllowed();
 
 	START_CRIT_SECTION();
 
@@ -7704,8 +7715,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 462e23503e..941f449a3e 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -27,6 +27,7 @@
 #include <zstd.h>
 #endif
 
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
@@ -153,9 +154,20 @@ XLogBeginInsert(void)
 	Assert(mainrdata_last == (XLogRecData *) &mainrdata_head);
 	Assert(mainrdata_len == 0);
 
-	/* cross-check on whether we should be here or not */
-	if (!XLogInsertAllowed())
-		elog(ERROR, "cannot make new WAL entries during recovery");
+	/*
+	 * WAL permission must have checked before entering the critical section.
+	 * Otherwise, WAL prohibited error will force system panic.
+	 */
+	Assert(walprohibit_checked_state != WALPROHIBIT_UNCHECKED ||
+		   CritSectionCount == 0);
+
+	/*
+	 * Cross-check on whether we should be here or not.
+	 *
+	 * This check is primarily for a non-critical section that never insists the
+	 * same WAL write permission check before reaching here.
+	 */
+	CheckWALPermitted();
 
 	if (begininsert_called)
 		elog(ERROR, "XLogBeginInsert was already called");
@@ -233,6 +245,9 @@ XLogResetInsertion(void)
 	mainrdata_last = (XLogRecData *) &mainrdata_head;
 	curinsert_flags = 0;
 	begininsert_called = false;
+
+	/* Reset walpermit_checked flag */
+	RESET_WALPROHIBIT_CHECKED_STATE();
 }
 
 /*
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index ce776c53ca..3b7a3d1fe9 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -28,6 +28,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/tableam.h"
+#include "access/walprohibit.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -477,6 +478,8 @@ CreateDirAndVersionFile(char *dbpath, Oid dbid, Oid tsid, bool isRedo)
 		xl_dbase_create_wal_log_rec xlrec;
 		XLogRecPtr	lsn;
 
+		AssertWALPermittedHaveXID();
+
 		START_CRIT_SECTION();
 
 		xlrec.db_id = dbid;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ddf219b21f..411f12fc25 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"
@@ -400,6 +401,9 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
 	if (RelationNeedsWAL(rel))
 		GetTopTransactionId();
 
+	/* Cannot have valid XID without WAL permission */
+	AssertWALPermittedHaveXID();
+
 	START_CRIT_SECTION();
 
 	MarkBufferDirty(buf);
@@ -640,6 +644,7 @@ nextval_internal(Oid relid, bool check_permissions)
 				rescnt = 0;
 	bool		cycle;
 	bool		logit = false;
+	bool		needwal;
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -799,9 +804,15 @@ nextval_internal(Oid relid, bool check_permissions)
 	 * to assign xids subxacts, that'll already trigger an appropriate wait.
 	 * (Have to do that here, so we're outside the critical section)
 	 */
-	if (logit && RelationNeedsWAL(seqrel))
+	needwal = logit && RelationNeedsWAL(seqrel);
+	if (needwal)
+	{
 		GetTopTransactionId();
 
+		/* Cannot have valid XID without WAL permission */
+		AssertWALPermittedHaveXID();
+	}
+
 	/* ready to change the on-disk (or really, in-buffer) tuple */
 	START_CRIT_SECTION();
 
@@ -817,7 +828,7 @@ nextval_internal(Oid relid, bool check_permissions)
 	MarkBufferDirty(buf);
 
 	/* XLOG stuff */
-	if (logit && RelationNeedsWAL(seqrel))
+	if (needwal)
 	{
 		xl_seq_rec	xlrec;
 		XLogRecPtr	recptr;
@@ -951,6 +962,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	Form_pg_sequence pgsform;
 	int64		maxv,
 				minv;
+	bool		needwal;
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -1001,9 +1013,15 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	elm->cached = elm->last;
 
 	/* check the comment above nextval_internal()'s equivalent call. */
-	if (RelationNeedsWAL(seqrel))
+	needwal = RelationNeedsWAL(seqrel);
+	if (needwal)
+	{
 		GetTopTransactionId();
 
+		/* Cannot have valid XID without WAL permission */
+		AssertWALPermittedHaveXID();
+	}
+
 	/* ready to change the on-disk (or really, in-buffer) tuple */
 	START_CRIT_SECTION();
 
@@ -1014,7 +1032,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	MarkBufferDirty(buf);
 
 	/* XLOG stuff */
-	if (RelationNeedsWAL(seqrel))
+	if (needwal)
 	{
 		xl_seq_rec	xlrec;
 		XLogRecPtr	recptr;
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 09dd645bca..4d8c965043 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -955,6 +955,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 93c1ea2d9f..a8afb0dd46 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -34,6 +34,7 @@
 #include <unistd.h>
 
 #include "access/tableam.h"
+#include "access/walprohibit.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
@@ -3748,6 +3749,8 @@ RelationCopyStorageUsingBuffer(Relation src, Relation dst, ForkNumber forkNum,
 										   permanent);
 		LockBuffer(dstBuf, BUFFER_LOCK_EXCLUSIVE);
 
+		AssertWALPermittedHaveXID();
+
 		START_CRIT_SECTION();
 
 		/* Copy page data from the source to the destination. */
@@ -4038,13 +4041,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 d41ae37090..576a69da3f 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/xloginsert.h"
 #include "access/xlogutils.h"
 #include "miscadmin.h"
@@ -285,12 +286,19 @@ 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());
+
+		if (needwal)
+			CheckWALPermitted();
+
 		/* NO EREPORT(ERROR) from here till changes are logged */
 		START_CRIT_SECTION();
 
@@ -305,7 +313,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/utils/cache/relmapper.c b/src/backend/utils/cache/relmapper.c
index dee3387d02..91e963bff1 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"
@@ -905,6 +906,8 @@ write_relmap_file(RelMapFile *newmap, bool write_wal, bool send_sinval,
 		xl_relmap_update xlrec;
 		XLogRecPtr	lsn;
 
+		AssertWALPermittedHaveXID();
+
 		/* now errors are fatal ... */
 		START_CRIT_SECTION();
 
diff --git a/src/include/access/walprohibit.h b/src/include/access/walprohibit.h
index d71522cbf3..41bc221dbf 100644
--- a/src/include/access/walprohibit.h
+++ b/src/include/access/walprohibit.h
@@ -13,6 +13,7 @@
 
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "access/xlogutils.h"
 #include "miscadmin.h"
 #include "nodes/parsenodes.h"
 
@@ -49,6 +50,49 @@ CounterGetWALProhibitState(uint32 wal_prohibit_counter)
 	return (WALProhibitState) (wal_prohibit_counter & 3);
 }
 
+/* Never reaches when WAL is prohibited. */
+static inline void
+AssertWALPermitted(void)
+{
+	Assert(XLogInsertAllowed());
+
+#ifdef USE_ASSERT_CHECKING
+	walprohibit_checked_state = WALPROHIBIT_CHECKED;
+#endif
+}
+
+/*
+ * XID-bearing transactions are killed off by executing pg_prohibit_wal()
+ * function, 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
+AssertWALPermittedHaveXID(void)
+{
+	/* Must be performing an INSERT, UPDATE or DELETE, so we'll have an XID */
+	Assert(FullTransactionIdIsValid(GetTopFullTransactionIdIfAny()));
+	AssertWALPermitted();
+}
+
+/*
+ * In opposite to the above assertion if a transaction doesn't have valid XID
+ * (e.g. VACUUM) 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("WAL is now prohibited")));
+
+#ifdef USE_ASSERT_CHECKING
+	walprohibit_checked_state = WALPROHIBIT_CHECKED;
+#endif
+}
+
 extern bool ProcessBarrierWALProhibit(void);
 extern void MarkCheckPointSkippedInWalProhibitState(void);
 extern void WALProhibitStateCounterInit(bool wal_prohibited);
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index bcf2016421..4510d01db8 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -107,6 +107,30 @@ extern PGDLLIMPORT volatile uint32 CritSectionCount;
 /* in tcop/postgres.c */
 extern void ProcessInterrupts(void);
 
+#ifdef USE_ASSERT_CHECKING
+typedef enum
+{
+	WALPROHIBIT_UNCHECKED,
+	WALPROHIBIT_CHECKED,
+	WALPROHIBIT_CHECKED_AND_USED
+} WALProhibitCheckState;
+
+/* in access/walprohibit.c */
+extern PGDLLIMPORT WALProhibitCheckState walprohibit_checked_state;
+
+/*
+ * Reset walpermit_checked flag when no longer in the critical section.
+ * Otherwise, marked checked and used.
+ */
+#define RESET_WALPROHIBIT_CHECKED_STATE() \
+do { \
+	walprohibit_checked_state = (CritSectionCount == 0) ? \
+	WALPROHIBIT_UNCHECKED : WALPROHIBIT_CHECKED_AND_USED; \
+} while(0)
+#else
+#define RESET_WALPROHIBIT_CHECKED_STATE() ((void) 0)
+#endif
+
 /* Test whether an interrupt is pending */
 #ifndef WIN32
 #define INTERRUPTS_PENDING_CONDITION() \
@@ -122,6 +146,7 @@ extern void ProcessInterrupts(void);
 do { \
 	if (INTERRUPTS_PENDING_CONDITION()) \
 		ProcessInterrupts(); \
+	RESET_WALPROHIBIT_CHECKED_STATE(); \
 } while(0)
 
 /* Is ProcessInterrupts() guaranteed to clear InterruptPending? */
@@ -151,6 +176,7 @@ do { \
 do { \
 	Assert(CritSectionCount > 0); \
 	CritSectionCount--; \
+	RESET_WALPROHIBIT_CHECKED_STATE(); \
 } while(0)
 
 
-- 
2.18.0

