From 36d412774b4c6a3133957a6891fb0d53cbbd91f4 Mon Sep 17 00:00:00 2001
From: Ronan Dunklau <ronan.dunklau@aiven.io>
Date: Wed, 6 Mar 2024 09:12:31 +0100
Subject: [PATCH] Detect invalid FSM when finding a block.

Whenever the FSM points to a block past the end of the relation, detect
it and fallback to the relation extension mechanism.

This may arise when a FPI for the FSM has been triggered, and we end up
WAL-logging a page of the FSM pointing to newly extended blocks in the
relation which have never been WAL-logged.
---
 src/backend/access/heap/hio.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index ccc4c6966a2..623406d57e7 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -517,6 +517,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
 				otherBlock;
 	bool		unlockedTargetBuffer;
 	bool		recheckVmPins;
+	BlockNumber nblocks = InvalidBlockNumber;
 
 	len = MAXALIGN(len);		/* be conservative */
 
@@ -582,7 +583,17 @@ RelationGetBufferForTuple(Relation relation, Size len,
 		 * We have no cached target page, so ask the FSM for an initial
 		 * target.
 		 */
+		nblocks = RelationGetNumberOfBlocks(relation);
 		targetBlock = GetPageWithFreeSpace(relation, targetFreeSpace);
+		/*
+		 * Since relation extension are not wal logged, the fsm could end up
+		 * pointing to an unlogged empty segment.
+		 * Detect it, and fall back through trying the last page
+		 */
+		if (unlikely(targetBlock >= nblocks))
+		{
+			targetBlock = InvalidBlockNumber;
+		}
 	}
 
 	/*
@@ -592,7 +603,8 @@ RelationGetBufferForTuple(Relation relation, Size len,
 	 */
 	if (targetBlock == InvalidBlockNumber)
 	{
-		BlockNumber nblocks = RelationGetNumberOfBlocks(relation);
+		if (nblocks == InvalidBlockNumber)
+			nblocks = RelationGetNumberOfBlocks(relation);
 
 		if (nblocks > 0)
 			targetBlock = nblocks - 1;
@@ -761,6 +773,12 @@ loop:
 														targetBlock,
 														pageFreeSpace,
 														targetFreeSpace);
+			/* As above, if the FSM gives us a page past the end of the relation,
+			 * give up and fallback to extending the relation */
+			if (nblocks == InvalidBlockNumber)
+				nblocks = RelationGetNumberOfBlocks(relation);
+			if (targetBlock >= nblocks)
+				targetBlock = InvalidBlockNumber;
 		}
 	}
 
-- 
2.44.0

