From a45c70acf35e43257d86313dcbb7bb0e5201fab1 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Sun, 28 Jun 2015 21:59:29 +0300
Subject: [PATCH] Fix double-XLogBeginInsert call in GIN page splits.

If data checksums or wal_log_hints is on, and a GIN page is split, the code
to find a new, empty, block was called after having already called
XLogBeginInsert(). That causes an assertion failure or PANIC, if finding the
new block involves updating a FSM page that had not been modified since last
checkpoint, because that update is WAL-logged, which calls XLogBeginInsert
again. Nested XLogBeginInsert calls are not supported.

To fix, rearrange GIN code so that XLogBeginInsert is called later, after
finding the victim buffers.

Reported by Jeff Janes.
---
 src/backend/access/gin/ginbtree.c     | 15 ++++++---------
 src/backend/access/gin/gindatapage.c  |  4 ++++
 src/backend/access/gin/ginentrypage.c |  1 +
 3 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 3f92c56d3c0..f0ff91aba2f 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -358,20 +358,15 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 	 * placeToPage can register some data to the WAL record.
 	 *
 	 * If placeToPage returns INSERTED, placeToPage has already called
-	 * START_CRIT_SECTION(), and we're responsible for calling
-	 * END_CRIT_SECTION. When it returns INSERTED, it is also responsible for
-	 * registering any data required to replay the operation with
-	 * XLogRegisterData(0, ...). It may only add data to block index 0; the
-	 * main data of the WAL record is reserved for this function.
+	 * START_CRIT_SECTION() and XLogBeginInsert(), and registered any data
+	 * required to replay the operation, in block index 0. We're responsible
+	 * for filling in the main data portion of the WAL record, calling
+	 * XLogInsert(), and END_CRIT_SECTION.
 	 *
 	 * If placeToPage returns SPLIT, we're wholly responsible for WAL logging.
 	 * Splits happen infrequently, so we just make a full-page image of all
 	 * the pages involved.
 	 */
-
-	if (RelationNeedsWAL(btree->index))
-		XLogBeginInsert();
-
 	rc = btree->placeToPage(btree, stack->buffer, stack,
 							insertdata, updateblkno,
 							&newlpage, &newrpage);
@@ -558,6 +553,8 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 		{
 			XLogRecPtr	recptr;
 
+			XLogBeginInsert();
+
 			/*
 			 * We just take full page images of all the split pages. Splits
 			 * are uncommon enough that it's not worth complicating the code
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index 8e81f6c8687..ec8c94bcbd1 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -600,7 +600,10 @@ dataPlaceToPageLeaf(GinBtree btree, Buffer buf, GinBtreeStack *stack,
 		 */
 		MemoryContextSwitchTo(oldCxt);
 		if (RelationNeedsWAL(btree->index))
+		{
+			XLogBeginInsert();
 			registerLeafRecompressWALData(buf, leaf);
+		}
 		START_CRIT_SECTION();
 		dataPlaceToPageLeafRecompress(buf, leaf);
 
@@ -1120,6 +1123,7 @@ dataPlaceToPageInternal(GinBtree btree, Buffer buf, GinBtreeStack *stack,
 		data.offset = off;
 		data.newitem = *pitem;
 
+		XLogBeginInsert();
 		XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
 		XLogRegisterBufData(0, (char *) &data,
 							sizeof(ginxlogInsertDataInternal));
diff --git a/src/backend/access/gin/ginentrypage.c b/src/backend/access/gin/ginentrypage.c
index 9997eae1bcd..c912e60a112 100644
--- a/src/backend/access/gin/ginentrypage.c
+++ b/src/backend/access/gin/ginentrypage.c
@@ -557,6 +557,7 @@ entryPlaceToPage(GinBtree btree, Buffer buf, GinBtreeStack *stack,
 		data.isDelete = insertData->isDelete;
 		data.offset = off;
 
+		XLogBeginInsert();
 		XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
 		XLogRegisterBufData(0, (char *) &data,
 							offsetof(ginxlogInsertEntry, tuple));
-- 
GitLab