From 40c1c7e76e7ad51189e07fdbadcd1e20ca49dd22 Mon Sep 17 00:00:00 2001
From: David Christensen <david.christensen@crunchydata.com>
Date: Fri, 5 Jan 2024 15:39:43 -0500
Subject: [PATCH v4 12/22] feature: Calculate all blocksize constants in a
 common location

These variables are calculated once on server startup or in front-end code which
utilizes these constants.

This adds the BlockSizeInit() and BlockSizeInitControl() routines to handle the
bootstrapping of the cluster constants depending on if we have a control file or
not.

Also move the declaration of ReservedPageSize variable into this module, since
we need the same sort of initialization if we are using these constants in the
frontend code as well.
---
 src/backend/access/transam/xlog.c  | 25 +++++++--
 src/backend/bootstrap/bootstrap.c  |  4 +-
 src/backend/storage/page/bufpage.c |  1 -
 src/common/Makefile                |  1 +
 src/common/blocksize.c             | 88 ++++++++++++++++++++++++++++++
 src/common/meson.build             |  1 +
 src/include/access/heaptoast.h     |  3 +-
 src/include/access/htup_details.h  |  2 -
 src/include/access/itup.h          |  1 -
 src/include/access/nbtree_int.h    |  1 -
 src/include/common/blocksize.h     | 37 +++++++++++++
 src/include/common/blocksize_int.h | 42 ++++++++++++++
 src/include/storage/bufpage.h      |  3 +-
 13 files changed, 194 insertions(+), 15 deletions(-)
 create mode 100644 src/common/blocksize.c
 create mode 100644 src/include/common/blocksize.h
 create mode 100644 src/include/common/blocksize_int.h

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b372e4548d..93e435f820 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4187,7 +4187,7 @@ WriteControlFile(void)
 	ControlFile->indexMaxKeys = INDEX_MAX_KEYS;
 
 	ControlFile->toast_max_chunk_size = ClusterToastMaxChunkSize;
-	ControlFile->loblksize = LOBLKSIZE;
+	ControlFile->loblksize = ClusterLargeObjectBlockSize;
 
 	ControlFile->float8ByVal = FLOAT8PASSBYVAL;
 
@@ -4251,7 +4251,6 @@ ReadControlFile(void)
 	pg_crc32c	crc;
 	int			fd;
 	static char wal_segsz_str[20];
-	static char reserved_page_size_str[20];
 	int			r;
 	int reserved_page_size;
 	/*
@@ -4319,7 +4318,23 @@ ReadControlFile(void)
 		ereport(FATAL,
 				(errmsg("incorrect checksum in control file")));
 
+	/*
+	 * Block size computations affect a number of things that are later
+	 * checked, so ensure that we calculate as soon as CRC has been validated
+	 * before checking other things that may depend on it.
+	 */
+
 	reserved_page_size = ControlFile->reserved_page_size;
+
+	if (!IsValidReservedPageSize(reserved_page_size))
+		ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg_plural("Reserved Page Size must be a multiple of 8 between 0 and 256, but the control file specifies %d byte",
+									  "Reserved Page Size must be a multiple of 8 between 0 and 256, but the control file specifies %d bytes",
+									  reserved_page_size,
+									  reserved_page_size)));
+
+	BlockSizeInit(ControlFile->blcksz, reserved_page_size);
+
 	/*
 	 * Do compatibility checking immediately.  If the database isn't
 	 * compatible with the backend executable, we want to abort before we can
@@ -4386,12 +4401,12 @@ ReadControlFile(void)
 						   " but the server was configured with ClusterToastMaxChunkSize %d.",
 						   ControlFile->toast_max_chunk_size, (int) ClusterToastMaxChunkSize),
 				 errhint("It looks like you need to recompile or initdb.")));
-	if (ControlFile->loblksize != LOBLKSIZE)
+	if (ControlFile->loblksize != ClusterLargeObjectBlockSize)
 		ereport(FATAL,
 				(errmsg("database files are incompatible with server"),
 				 errdetail("The database cluster was initialized with LOBLKSIZE %d,"
-						   " but the server was compiled with LOBLKSIZE %d.",
-						   ControlFile->loblksize, (int) LOBLKSIZE),
+						   " but the server was configured with LOBLKSIZE %d.",
+						   ControlFile->loblksize, (int) ClusterLargeObjectBlockSize),
 				 errhint("It looks like you need to recompile or initdb.")));
 
 #ifdef USE_FLOAT8_BYVAL
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 986f6f1d9c..20d9e786d8 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -42,7 +42,7 @@
 #include "utils/relmapper.h"
 
 uint32		bootstrap_data_checksum_version = 0;	/* No checksum */
-
+Size		bootstrap_reserved_page_size = 0;		/* No reserved size */
 
 static void CheckerModeMain(void);
 static void bootstrap_signals(void);
@@ -308,6 +308,8 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
 	SetProcessingMode(BootstrapProcessing);
 	IgnoreSystemIndexes = true;
 
+	BlockSizeInit(BLCKSZ, bootstrap_reserved_page_size);
+
 	InitializeMaxBackends();
 
 	CreateSharedMemoryAndSemaphores();
diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c
index 4aba6e8081..8a5fb66a8e 100644
--- a/src/backend/storage/page/bufpage.c
+++ b/src/backend/storage/page/bufpage.c
@@ -26,7 +26,6 @@
 /* GUC variable */
 bool		ignore_checksum_failure = false;
 
-int ReservedPageSize = 0;
 /* ----------------------------------------------------------------
  *						Page support functions
  * ----------------------------------------------------------------
diff --git a/src/common/Makefile b/src/common/Makefile
index 3d83299432..9276b9f8ae 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -48,6 +48,7 @@ OBJS_COMMON = \
 	base64.o \
 	binaryheap.o \
 	blkreftable.o \
+	blocksize.o \
 	checksum_helper.o \
 	compression.o \
 	config_info.o \
diff --git a/src/common/blocksize.c b/src/common/blocksize.c
new file mode 100644
index 0000000000..1dc3e0bbe0
--- /dev/null
+++ b/src/common/blocksize.c
@@ -0,0 +1,88 @@
+/*-------------------------------------------------------------------------
+ *
+ * blocksize.c
+ *		This file contains methods to calculate blocksize-related variables
+ *
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/common/blocksize.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "common/blocksize_int.h"
+#include "access/heaptoast.h"
+#include "access/htup_details.h"
+#include "access/itup.h"
+#include "access/nbtree_int.h"
+#include "common/controldata_utils.h"
+#include "storage/large_object.h"
+
+/* These variables are effectively constants, but are initialized by BlockSizeInit() */
+
+Size ReservedPageSize;
+
+Size ClusterMaxTIDsPerBTreePage = 0;
+Size ClusterLargeObjectBlockSize = 0;
+Size ClusterMaxHeapTupleSize = 0;
+Size ClusterMaxHeapTuplesPerPage = 0;
+Size ClusterMaxIndexTuplesPerPage = 0;
+Size ClusterToastMaxChunkSize = 0;
+
+/*
+ * This routine will calculate and cache the necessary constants. This should
+ * be called once very very early in the process (as soon as the native block
+ * size is known, so after reading ControlFile, or using BlockSizeInitControl).
+ */
+
+static bool initialized = false;
+
+void
+BlockSizeInit(Size rawblocksize, Size reservedsize)
+{
+	Assert(rawblocksize == BLCKSZ);
+	Assert(IsValidReservedPageSize(reservedsize));
+
+	if (initialized)
+		return;
+
+	ReservedPageSize = reservedsize;
+	ClusterMaxTIDsPerBTreePage = CalcMaxTIDsPerBTreePage(PageUsableSpace);
+	ClusterLargeObjectBlockSize = LOBLKSIZE;		 /* TODO: calculate? */
+	ClusterMaxHeapTupleSize = CalcMaxHeapTupleSize(PageUsableSpace);
+	ClusterMaxHeapTuplesPerPage = CalcMaxHeapTuplesPerPage(PageUsableSpace);
+	ClusterMaxIndexTuplesPerPage = CalcMaxIndexTuplesPerPage(PageUsableSpace);
+	ClusterToastMaxChunkSize = CalcToastMaxChunkSize(PageUsableSpace);
+
+	initialized = true;
+}
+
+/*
+ * Init the BlockSize using values from the given control file pointer.  If
+ * this is nil, then load and use the control file pointed to by the pgdata
+ * path and perform said operations.
+ */
+void
+BlockSizeInitControl(ControlFileData *control, const char *pgdata)
+{
+	bool crc_ok = true;
+
+	Assert(pgdata);
+
+	if (!control)
+		control = get_controlfile(pgdata, &crc_ok);
+
+	Assert(crc_ok);
+
+	if (control)
+	{
+		BlockSizeInit(control->blcksz, control->reserved_page_size);
+		return;
+	}
+
+	/* panic */
+}
diff --git a/src/common/meson.build b/src/common/meson.build
index de68e408fa..3a711a06a4 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -5,6 +5,7 @@ common_sources = files(
   'base64.c',
   'binaryheap.c',
   'blkreftable.c',
+  'blocksize.c',
   'checksum_helper.c',
   'compression.c',
   'controldata_utils.c',
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index b0a85ec4a1..96b719d9a3 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -24,7 +24,7 @@
 	MAXALIGN_DOWN(((usablespace) - ((tuplesPerPage) * sizeof(ItemIdData))) \
 				  / (tuplesPerPage))
 
-#define MaximumBytesPerTuple(tuplesPerPage) CalcMaximumBytesPerTuple(PageUsableSpaceMax,tuplesPerPage)
+#define MaximumBytesPerTuple(tuplesPerPage) CalcMaximumBytesPerTuple(PageUsableSpace,tuplesPerPage)
 
 /*
  * These symbols control toaster activation.  If a tuple is larger than
@@ -91,7 +91,6 @@
 	 VARHDRSZ)
 
 #define TOAST_MAX_CHUNK_SIZE_LIMIT CalcToastMaxChunkSize(PageUsableSpaceMax)
-#define ClusterToastMaxChunkSize CalcToastMaxChunkSize(PageUsableSpace)
 
 /* ----------
  * heap_toast_insert_or_update -
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 57dd928bda..0c41170ed4 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -569,7 +569,6 @@ StaticAssertDecl(MaxOffsetNumber < SpecTokenOffsetNumber,
  * you can, say, fit 2 tuples of size ClusterMaxHeapTupleSize/2 on the same page.
  */
 #define CalcMaxHeapTupleSize(size)  (size - sizeof(ItemIdData))
-#define ClusterMaxHeapTupleSize CalcMaxHeapTupleSize(PageUsableSpace)
 #define MaxHeapTupleSizeLimit CalcMaxHeapTupleSize(PageUsableSpaceMax)
 #define MinHeapTupleSize  MAXALIGN(SizeofHeapTupleHeader)
 
@@ -599,7 +598,6 @@ StaticAssertDecl(MaxOffsetNumber < SpecTokenOffsetNumber,
  */
 #define CalcMaxHeapTuplesPerPage(size)	((int) ((size) / \
 			(MAXALIGN(SizeofHeapTupleHeader) + sizeof(ItemIdData))))
-#define ClusterMaxHeapTuplesPerPage CalcMaxHeapTuplesPerPage(PageUsableSpace)
 #define MaxHeapTuplesPerPageLimit CalcMaxHeapTuplesPerPage(PageUsableSpaceMax)
 
 /*
diff --git a/src/include/access/itup.h b/src/include/access/itup.h
index 9c436c8b16..3ef6ceb9f0 100644
--- a/src/include/access/itup.h
+++ b/src/include/access/itup.h
@@ -173,7 +173,6 @@ index_getattr(IndexTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
  */
 #define CalcMaxIndexTuplesPerPage(size)	((int) ((size) / \
 			(MAXALIGN(sizeof(IndexTupleData) + 1) + sizeof(ItemIdData))))
-#define ClusterMaxIndexTuplesPerPage CalcMaxIndexTuplesPerPage(PageUsableSpace)
 #define MaxIndexTuplesPerPageLimit CalcMaxIndexTuplesPerPage(PageUsableSpaceMax)
 
 #endif							/* ITUP_H */
diff --git a/src/include/access/nbtree_int.h b/src/include/access/nbtree_int.h
index b40bc89fa0..8e0249ca96 100644
--- a/src/include/access/nbtree_int.h
+++ b/src/include/access/nbtree_int.h
@@ -186,7 +186,6 @@ typedef struct BTMetaPageData
 #define CalcMaxTIDsPerBTreePage(usablespace)			  \
 	(int) ((usablespace) - sizeof(BTPageOpaqueData) / \
 		   sizeof(ItemPointerData))
-#define ClusterMaxTIDsPerBTreePage (CalcMaxTIDsPerBTreePage(PageUsableSpace))
 #define MaxTIDsPerBTreePageLimit (CalcMaxTIDsPerBTreePage(PageUsableSpaceMax))
 
 #endif
diff --git a/src/include/common/blocksize.h b/src/include/common/blocksize.h
new file mode 100644
index 0000000000..ff9e0cd3d6
--- /dev/null
+++ b/src/include/common/blocksize.h
@@ -0,0 +1,37 @@
+/*-------------------------------------------------------------------------
+ *
+ * blocksize.h
+ *	  definitions for cluster-specific limits/structure defs
+ *
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION: src/include/common/blocksize.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef BLOCKSIZE_H
+#define BLOCKSIZE_H
+
+#include "catalog/pg_control.h"
+
+void BlockSizeInit(Size rawblocksize, Size reservedsize);
+void BlockSizeInitControl(ControlFileData *ControlFile, const char *DataDir);
+
+/* These constants are initialized at runtime but are effective constants for callers */
+
+const extern PGDLLIMPORT Size ReservedPageSize;
+const extern PGDLLIMPORT Size ClusterMaxTIDsPerBTreePage;
+const extern PGDLLIMPORT Size ClusterLargeObjectBlockSize;
+const extern PGDLLIMPORT Size ClusterMaxHeapTupleSize;
+const extern PGDLLIMPORT Size ClusterMaxHeapTuplesPerPage;
+const extern PGDLLIMPORT Size ClusterMaxIndexTuplesPerPage;
+const extern PGDLLIMPORT Size ClusterToastMaxChunkSize;
+
+#define MaxReservedPageSize 256
+
+/* between 0 and MaxReservedPageSize and multiple of 8 */
+#define IsValidReservedPageSize(s) ((s) >= 0 && (s) <= MaxReservedPageSize && (((s)&0x7) == 0))
+
+#endif
diff --git a/src/include/common/blocksize_int.h b/src/include/common/blocksize_int.h
new file mode 100644
index 0000000000..a7213878f7
--- /dev/null
+++ b/src/include/common/blocksize_int.h
@@ -0,0 +1,42 @@
+/*-------------------------------------------------------------------------
+ *
+ * blocksize_int.h
+ *	  internal defintions for cluster-specific limits/structure defs
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION: src/include/common/blocksize_int.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Note: We use the same identifier here as in blocksize.h, due to blocksize.c
+ * (the only consumer of this header file) needing to see these definitions;
+ * other subsequent header files will pull in blocksize.h, so without using
+ * the same symbol you get conflicting defintion errors.
+ */
+
+#ifndef BLOCKSIZE_H
+#define BLOCKSIZE_H
+
+/* forward declaration */
+typedef struct ControlFileData ControlFileData;
+
+void BlockSizeInit(Size rawblocksize, Size reservedsize);
+void BlockSizeInitControl(ControlFileData *ControlFile, const char *DataDir);
+
+extern PGDLLIMPORT Size ReservedPageSize;
+extern PGDLLIMPORT Size ClusterMaxTIDsPerBTreePage;
+extern PGDLLIMPORT Size ClusterLargeObjectBlockSize;
+extern PGDLLIMPORT Size ClusterMaxHeapTupleSize;
+extern PGDLLIMPORT Size ClusterMaxHeapTuplesPerPage;
+extern PGDLLIMPORT Size ClusterMaxIndexTuplesPerPage;
+extern PGDLLIMPORT Size ClusterToastMaxChunkSize;
+
+#define MaxReservedPageSize 256
+
+/* between 0 and MaxReservedPageSize and multiple of 8 */
+#define IsValidReservedPageSize(s) ((s) >= 0 && (s) <= MaxReservedPageSize && (((s)&0x7) == 0))
+
+#endif
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 8d62544830..9cb04f6ad8 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -15,6 +15,7 @@
 #define BUFPAGE_H
 
 #include "access/xlogdefs.h"
+#include "common/blocksize.h"
 #include "storage/block.h"
 #include "storage/item.h"
 #include "storage/off.h"
@@ -216,8 +217,6 @@ typedef PageHeaderData *PageHeader;
 /*
  * how much space is left after smgr's bookkeeping, etc; should be MAXALIGN
  */
-extern int ReservedPageSize;
-
 #define PageUsableSpace (BLCKSZ - SizeOfPageHeaderData - ReservedPageSize)
 #define PageUsableSpaceMax (BLCKSZ - SizeOfPageHeaderData)
 
-- 
2.40.1

