From 2de281da212e16ea198f0c8e3165a4d67fe5e7a4 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Thu, 10 Dec 2015 16:25:12 +0100
Subject: [PATCH] Fix bug leading to restoring unlogged relations from empty
 files.

At the end of crash recovery, unlogged relations are reset to the empty
state, using their init fork as the template. The init fork is copied to
the main fork without going through shared buffers. Unfortunately WAL
replay so far has not necessarily flushed writes from shared buffers to
disk at that point. In normal crash recovery, and before the
introduction of 'fast promotions' in fd4ced523 / 9.3, the
END_OF_RECOVERY checkpoint flushes the buffers out in time. But with
fast promotions that's not the case anymore.

To fix, force WAL writes targeting the init fork to be flushed
immediately (using the new FlushOneBuffer() function). In 9.5+ that
flush can centrally be triggered from the code dealing with restoring
full page writes (XLogReadBufferForRedoExtended), in earlier releases
that responsibility is in the hands of XLOG_HEAP_NEWPAGE's replay
function.

Backpatch to 9.1, even if this currently is only known to trigger in
9.3+. Flushing earlier is more robust, and it is advantageous to keep
the branches similar.

Typical symptoms of this bug are errors like
'ERROR:  index "..." contains unexpected zero page at block 0'
shortly after promoting a node.

Reported-By: Thom Brown
Author: Andres Freund and Michael Paquier
Discussion: 20150326175024.GJ451@alap3.anarazel.de
Backpatch: 9.1-
---
 src/backend/access/heap/heapam.c    | 10 ++++++++++
 src/backend/storage/buffer/bufmgr.c | 21 +++++++++++++++++++++
 src/include/storage/bufmgr.h        |  1 +
 3 files changed, 32 insertions(+)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index cb4289bba29..eb8eada27d6 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -4941,6 +4941,16 @@ heap_xlog_newpage(XLogRecPtr lsn, XLogRecord *record)
 	}
 
 	MarkBufferDirty(buffer);
+
+	/*
+	 * At the end of crash recovery the init forks of unlogged relations are
+	 * copied, without going through shared buffers. So we need to force the
+	 * on-disk state of init forks to always be in sync with the state in
+	 * shared buffers.
+	 */
+	if (xlrec->forknum == INIT_FORKNUM)
+		FlushOneBuffer(buffer);
+
 	UnlockReleaseBuffer(buffer);
 }
 
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9b881ff690..81b9b297724 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -2396,6 +2396,27 @@ FlushDatabaseBuffers(Oid dbid)
 	}
 }
 
+/*
+ * Flush a previously, shared or exclusively, locked and pinned buffer to the
+ * OS.
+ */
+void
+FlushOneBuffer(Buffer buffer)
+{
+	volatile BufferDesc *bufHdr;
+
+	/* currently not needed, but no fundamental reason not to support */
+	Assert(!BufferIsLocal(buffer));
+
+	Assert(BufferIsPinned(buffer));
+
+	bufHdr = &BufferDescriptors[buffer - 1];
+
+	LWLockHeldByMe(bufHdr->content_lock);
+
+	FlushBuffer(bufHdr, NULL);
+}
+
 /*
  * ReleaseBuffer -- release the pin on a buffer
  */
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 4e2bd8866a9..f5bbe965640 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -191,6 +191,7 @@ extern void CheckPointBuffers(int flags);
 extern BlockNumber BufferGetBlockNumber(Buffer buffer);
 extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
 								ForkNumber forkNum);
+extern void FlushOneBuffer(Buffer buffer);
 extern void FlushRelationBuffers(Relation rel);
 extern void FlushDatabaseBuffers(Oid dbid);
 extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode,
-- 
GitLab