From 1599a09fce586e20d172a30bff72c5586260ba37 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <orlovmg@gmail.com>
Date: Wed, 25 Feb 2026 16:59:38 +0300
Subject: [PATCH v2 1/5] Add a callback for generating an I/O message in the
 SLRU

Historically, the SLRU module was designed to work with transaction IDs.
But now we use it to work with different objects, and even of different
types. However, I/O errors continued to be output in the
corresponding XIDs format.

This commit adds a callback that will allow to create custom IO error
messages for modules that don't work with transaction IDs.

No user-visible behavior change is expected in this commit.
---
 src/backend/access/transam/clog.c      |  8 ++---
 src/backend/access/transam/commit_ts.c |  5 +--
 src/backend/access/transam/multixact.c | 21 +++++++------
 src/backend/access/transam/slru.c      | 43 ++++++++++++++++----------
 src/backend/access/transam/subtrans.c  |  5 +--
 src/backend/commands/async.c           | 10 +++---
 src/backend/storage/lmgr/predicate.c   |  5 +--
 src/include/access/slru.h              | 26 ++++++++++++++--
 src/test/modules/test_slru/test_slru.c |  5 +--
 9 files changed, 83 insertions(+), 45 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index b5c38bbb162..1c62672d656 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -381,8 +381,7 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
 	 * write-busy, since we don't care if the update reaches disk sooner than
 	 * we think.
 	 */
-	slotno = SimpleLruReadPage(XactCtl, pageno, !XLogRecPtrIsValid(lsn),
-							   xid);
+	slotno = SimpleLruReadPage(XactCtl, pageno, !XLogRecPtrIsValid(lsn), &xid);
 
 	/*
 	 * Set the main transaction id, if any.
@@ -743,7 +742,7 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
 
 	/* lock is acquired by SimpleLruReadPage_ReadOnly */
 
-	slotno = SimpleLruReadPage_ReadOnly(XactCtl, pageno, xid);
+	slotno = SimpleLruReadPage_ReadOnly(XactCtl, pageno, &xid);
 	byteptr = XactCtl->shared->page_buffer[slotno] + byteno;
 
 	status = (*byteptr >> bshift) & CLOG_XACT_BITMASK;
@@ -807,6 +806,7 @@ CLOGShmemInit(void)
 	Assert(transaction_buffers != 0);
 
 	XactCtl->PagePrecedes = CLOGPagePrecedes;
+	XactCtl->IoErrorMsg = TransactionIdIoErrorMsg;
 	SimpleLruInit(XactCtl, "transaction", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE,
 				  "pg_xact", LWTRANCHE_XACT_BUFFER,
 				  LWTRANCHE_XACT_SLRU, SYNC_HANDLER_CLOG, false);
@@ -882,7 +882,7 @@ TrimCLOG(void)
 		int			slotno;
 		char	   *byteptr;
 
-		slotno = SimpleLruReadPage(XactCtl, pageno, false, xid);
+		slotno = SimpleLruReadPage(XactCtl, pageno, false, &xid);
 		byteptr = XactCtl->shared->page_buffer[slotno] + byteno;
 
 		/* Zero so-far-unused positions in the current byte */
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 6fa2178f1dd..96775885d39 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -227,7 +227,7 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids,
 
 	LWLockAcquire(lock, LW_EXCLUSIVE);
 
-	slotno = SimpleLruReadPage(CommitTsCtl, pageno, true, xid);
+	slotno = SimpleLruReadPage(CommitTsCtl, pageno, true, &xid);
 
 	TransactionIdSetCommitTs(xid, ts, nodeid, slotno);
 	for (i = 0; i < nsubxids; i++)
@@ -332,7 +332,7 @@ TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
 	}
 
 	/* lock is acquired by SimpleLruReadPage_ReadOnly */
-	slotno = SimpleLruReadPage_ReadOnly(CommitTsCtl, pageno, xid);
+	slotno = SimpleLruReadPage_ReadOnly(CommitTsCtl, pageno, &xid);
 	memcpy(&entry,
 		   CommitTsCtl->shared->page_buffer[slotno] +
 		   SizeOfCommitTimestampEntry * entryno,
@@ -551,6 +551,7 @@ CommitTsShmemInit(void)
 	Assert(commit_timestamp_buffers != 0);
 
 	CommitTsCtl->PagePrecedes = CommitTsPagePrecedes;
+	CommitTsCtl->IoErrorMsg = TransactionIdIoErrorMsg;
 	SimpleLruInit(CommitTsCtl, "commit_timestamp", CommitTsShmemBuffers(), 0,
 				  "pg_commit_ts", LWTRANCHE_COMMITTS_BUFFER,
 				  LWTRANCHE_COMMITTS_SLRU,
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 90ec87d9dd6..63d5256823e 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -798,7 +798,7 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
 	 * enough that a MultiXactId is really involved.  Perhaps someday we'll
 	 * take the trouble to generalize the slru.c error reporting code.
 	 */
-	slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi);
+	slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, &multi);
 	offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
 	offptr += entryno;
 
@@ -827,7 +827,7 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
 		lock = SimpleLruGetBankLock(MultiXactOffsetCtl, next_pageno);
 		LWLockAcquire(lock, LW_EXCLUSIVE);
 
-		slotno = SimpleLruReadPage(MultiXactOffsetCtl, next_pageno, true, next);
+		slotno = SimpleLruReadPage(MultiXactOffsetCtl, next_pageno, true, &next);
 		next_offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
 		next_offptr += next_entryno;
 	}
@@ -881,7 +881,7 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
 				LWLockAcquire(lock, LW_EXCLUSIVE);
 				prevlock = lock;
 			}
-			slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
+			slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, &multi);
 			prev_pageno = pageno;
 		}
 
@@ -1206,7 +1206,7 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
 	LWLockAcquire(lock, LW_EXCLUSIVE);
 
 	/* read this multi's offset */
-	slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi);
+	slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, &multi);
 	offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
 	offptr += entryno;
 	offset = *offptr;
@@ -1244,7 +1244,7 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
 				LWLockAcquire(newlock, LW_EXCLUSIVE);
 				lock = newlock;
 			}
-			slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, tmpMXact);
+			slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, &tmpMXact);
 		}
 
 		offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
@@ -1309,7 +1309,7 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
 				lock = newlock;
 			}
 
-			slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
+			slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, &multi);
 			prev_pageno = pageno;
 		}
 
@@ -1730,6 +1730,9 @@ MultiXactShmemInit(void)
 	MultiXactOffsetCtl->PagePrecedes = MultiXactOffsetPagePrecedes;
 	MultiXactMemberCtl->PagePrecedes = MultiXactMemberPagePrecedes;
 
+	MultiXactOffsetCtl->IoErrorMsg = TransactionIdIoErrorMsg;
+	MultiXactMemberCtl->IoErrorMsg = TransactionIdIoErrorMsg;
+
 	SimpleLruInit(MultiXactOffsetCtl,
 				  "multixact_offset", multixact_offset_buffers, 0,
 				  "pg_multixact/offsets", LWTRANCHE_MULTIXACTOFFSET_BUFFER,
@@ -1879,7 +1882,7 @@ TrimMultiXact(void)
 		if (entryno == 0 || nextMXact == FirstMultiXactId)
 			slotno = SimpleLruZeroPage(MultiXactOffsetCtl, pageno);
 		else
-			slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact);
+			slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, &nextMXact);
 		offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
 		offptr += entryno;
 
@@ -1914,7 +1917,7 @@ TrimMultiXact(void)
 
 		LWLockAcquire(lock, LW_EXCLUSIVE);
 		memberoff = MXOffsetToMemberOffset(offset);
-		slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, offset);
+		slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, &offset);
 		xidptr = (TransactionId *)
 			(MultiXactMemberCtl->shared->page_buffer[slotno] + memberoff);
 
@@ -2444,7 +2447,7 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result)
 		return false;
 
 	/* lock is acquired by SimpleLruReadPage_ReadOnly */
-	slotno = SimpleLruReadPage_ReadOnly(MultiXactOffsetCtl, pageno, multi);
+	slotno = SimpleLruReadPage_ReadOnly(MultiXactOffsetCtl, pageno, &multi);
 	offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
 	offptr += entryno;
 	offset = *offptr;
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 549c7e3e64b..a87b0fe830a 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -181,7 +181,8 @@ static void SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata);
 static bool SlruPhysicalReadPage(SlruCtl ctl, int64 pageno, int slotno);
 static bool SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno,
 								  SlruWriteAll fdata);
-static void SlruReportIOError(SlruCtl ctl, int64 pageno, TransactionId xid);
+static void SlruReportIOError(SlruCtl ctl, int64 pageno,
+							  const void *opaque_data);
 static int	SlruSelectLRUPage(SlruCtl ctl, int64 pageno);
 
 static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename,
@@ -525,11 +526,13 @@ SimpleLruWaitIO(SlruCtl ctl, int slotno)
  */
 int
 SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
-				  TransactionId xid)
+				  const void *opaque_data)
 {
 	SlruShared	shared = ctl->shared;
 	LWLock	   *banklock = SimpleLruGetBankLock(ctl, pageno);
 
+	Assert(ctl->IoErrorMsg != NULL);
+
 	Assert(LWLockHeldByMeInMode(banklock, LW_EXCLUSIVE));
 
 	/* Outer loop handles restart if we must wait for someone else's I/O */
@@ -601,7 +604,7 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
 
 		/* Now it's okay to ereport if we failed */
 		if (!ok)
-			SlruReportIOError(ctl, pageno, xid);
+			SlruReportIOError(ctl, pageno, opaque_data);
 
 		SlruRecentlyUsed(shared, slotno);
 
@@ -627,7 +630,7 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
  * It is unspecified whether the lock will be shared or exclusive.
  */
 int
-SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
+SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, const void *opaque_data)
 {
 	SlruShared	shared = ctl->shared;
 	LWLock	   *banklock = SimpleLruGetBankLock(ctl, pageno);
@@ -659,7 +662,7 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
 	LWLockRelease(banklock);
 	LWLockAcquire(banklock, LW_EXCLUSIVE);
 
-	return SimpleLruReadPage(ctl, pageno, true, xid);
+	return SimpleLruReadPage(ctl, pageno, true, opaque_data);
 }
 
 /*
@@ -681,7 +684,10 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
 	int			bankno = SlotGetBankNumber(slotno);
 	bool		ok;
 
+	Assert(ctl->IoErrorMsg != NULL);
+
 	Assert(shared->page_status[slotno] != SLRU_PAGE_EMPTY);
+
 	Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(ctl, pageno), LW_EXCLUSIVE));
 
 	/* If a write is in progress, wait for it to finish */
@@ -739,7 +745,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
 
 	/* Now it's okay to ereport if we failed */
 	if (!ok)
-		SlruReportIOError(ctl, pageno, InvalidTransactionId);
+		SlruReportIOError(ctl, pageno, NULL);
 
 	/* If part of a checkpoint, count this as a SLRU buffer written. */
 	if (fdata)
@@ -778,6 +784,8 @@ SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int64 pageno)
 	bool		result;
 	off_t		endpos;
 
+	Assert(ctl->IoErrorMsg != NULL);
+
 	/* update the stats counter of checked pages */
 	pgstat_count_slru_blocks_exists(ctl->shared->slru_stats_idx);
 
@@ -1070,12 +1078,13 @@ SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno, SlruWriteAll fdata)
  * SlruPhysicalWritePage.  Call this after cleaning up shared-memory state.
  */
 static void
-SlruReportIOError(SlruCtl ctl, int64 pageno, TransactionId xid)
+SlruReportIOError(SlruCtl ctl, int64 pageno, const void *opaque_data)
 {
 	int64		segno = pageno / SLRU_PAGES_PER_SEGMENT;
 	int			rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
 	int			offset = rpageno * BLCKSZ;
 	char		path[MAXPGPATH];
+	char	   *msg = ctl->IoErrorMsg(opaque_data);
 
 	SlruFileName(ctl, path, segno);
 	errno = slru_errno;
@@ -1084,13 +1093,13 @@ SlruReportIOError(SlruCtl ctl, int64 pageno, TransactionId xid)
 		case SLRU_OPEN_FAILED:
 			ereport(ERROR,
 					(errcode_for_file_access(),
-					 errmsg("could not access status of transaction %u", xid),
+					 errmsg("%s", msg),
 					 errdetail("Could not open file \"%s\": %m.", path)));
 			break;
 		case SLRU_SEEK_FAILED:
 			ereport(ERROR,
 					(errcode_for_file_access(),
-					 errmsg("could not access status of transaction %u", xid),
+					 errmsg("%s", msg),
 					 errdetail("Could not seek in file \"%s\" to offset %d: %m.",
 							   path, offset)));
 			break;
@@ -1098,38 +1107,38 @@ SlruReportIOError(SlruCtl ctl, int64 pageno, TransactionId xid)
 			if (errno)
 				ereport(ERROR,
 						(errcode_for_file_access(),
-						 errmsg("could not access status of transaction %u", xid),
+						 errmsg("%s", msg),
 						 errdetail("Could not read from file \"%s\" at offset %d: %m.",
 								   path, offset)));
 			else
 				ereport(ERROR,
-						(errmsg("could not access status of transaction %u", xid),
+						(errmsg("%s", msg),
 						 errdetail("Could not read from file \"%s\" at offset %d: read too few bytes.", path, offset)));
 			break;
 		case SLRU_WRITE_FAILED:
 			if (errno)
 				ereport(ERROR,
 						(errcode_for_file_access(),
-						 errmsg("could not access status of transaction %u", xid),
+						 errmsg("%s", msg),
 						 errdetail("Could not write to file \"%s\" at offset %d: %m.",
 								   path, offset)));
 			else
 				ereport(ERROR,
-						(errmsg("could not access status of transaction %u", xid),
+						(errmsg("%s", msg),
 						 errdetail("Could not write to file \"%s\" at offset %d: wrote too few bytes.",
 								   path, offset)));
 			break;
 		case SLRU_FSYNC_FAILED:
 			ereport(data_sync_elevel(ERROR),
 					(errcode_for_file_access(),
-					 errmsg("could not access status of transaction %u", xid),
+					 errmsg("%s", msg),
 					 errdetail("Could not fsync file \"%s\": %m.",
 							   path)));
 			break;
 		case SLRU_CLOSE_FAILED:
 			ereport(ERROR,
 					(errcode_for_file_access(),
-					 errmsg("could not access status of transaction %u", xid),
+					 errmsg("%s", msg),
 					 errdetail("Could not close file \"%s\": %m.",
 							   path)));
 			break;
@@ -1352,6 +1361,8 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
 	int			prevbank = SlotGetBankNumber(0);
 	bool		ok;
 
+	Assert(ctl->IoErrorMsg != NULL);
+
 	/* update the stats counter of flushes */
 	pgstat_count_slru_flush(shared->slru_stats_idx);
 
@@ -1411,7 +1422,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
 		}
 	}
 	if (!ok)
-		SlruReportIOError(ctl, pageno, InvalidTransactionId);
+		SlruReportIOError(ctl, pageno, NULL);
 
 	/* Ensure that directory entries for new files are on disk. */
 	if (ctl->sync_handler != SYNC_HANDLER_NONE)
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index c0987f43f11..37f1f74ab36 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -95,7 +95,7 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
 	lock = SimpleLruGetBankLock(SubTransCtl, pageno);
 	LWLockAcquire(lock, LW_EXCLUSIVE);
 
-	slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
+	slotno = SimpleLruReadPage(SubTransCtl, pageno, true, &xid);
 	ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
 	ptr += entryno;
 
@@ -135,7 +135,7 @@ SubTransGetParent(TransactionId xid)
 
 	/* lock is acquired by SimpleLruReadPage_ReadOnly */
 
-	slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
+	slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, &xid);
 	ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
 	ptr += entryno;
 
@@ -240,6 +240,7 @@ SUBTRANSShmemInit(void)
 	Assert(subtransaction_buffers != 0);
 
 	SubTransCtl->PagePrecedes = SubTransPagePrecedes;
+	SubTransCtl->IoErrorMsg = TransactionIdIoErrorMsg;
 	SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
 				  "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
 				  LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 657c591618d..a347996b373 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -829,6 +829,7 @@ AsyncShmemInit(void)
 	 * names are used in order to avoid wraparound.
 	 */
 	NotifyCtl->PagePrecedes = asyncQueuePagePrecedes;
+	NotifyCtl->IoErrorMsg = TransactionIdIoErrorMsg;
 	SimpleLruInit(NotifyCtl, "notify", notify_buffers, 0,
 				  "pg_notify", LWTRANCHE_NOTIFY_BUFFER, LWTRANCHE_NOTIFY_SLRU,
 				  SYNC_HANDLER_NONE, true);
@@ -2067,8 +2068,7 @@ asyncQueueAddEntries(ListCell *nextNotify)
 	if (QUEUE_POS_IS_ZERO(queue_head))
 		slotno = SimpleLruZeroPage(NotifyCtl, pageno);
 	else
-		slotno = SimpleLruReadPage(NotifyCtl, pageno, true,
-								   InvalidTransactionId);
+		slotno = SimpleLruReadPage(NotifyCtl, pageno, true, NULL);
 
 	/* Note we mark the page dirty before writing in it */
 	NotifyCtl->shared->page_dirty[slotno] = true;
@@ -2738,8 +2738,7 @@ asyncQueueProcessPageEntries(QueuePosition *current,
 	alignas(AsyncQueueEntry) char local_buf[QUEUE_PAGESIZE];
 	char	   *local_buf_end = local_buf;
 
-	slotno = SimpleLruReadPage_ReadOnly(NotifyCtl, curpage,
-										InvalidTransactionId);
+	slotno = SimpleLruReadPage_ReadOnly(NotifyCtl, curpage, NULL);
 	page_buffer = NotifyCtl->shared->page_buffer[slotno];
 
 	do
@@ -2997,8 +2996,7 @@ AsyncNotifyFreezeXids(TransactionId newFrozenXid)
 
 			lock = SimpleLruGetBankLock(NotifyCtl, pageno);
 			LWLockAcquire(lock, LW_EXCLUSIVE);
-			slotno = SimpleLruReadPage(NotifyCtl, pageno, true,
-									   InvalidTransactionId);
+			slotno = SimpleLruReadPage(NotifyCtl, pageno, true, NULL);
 			page_buffer = NotifyCtl->shared->page_buffer[slotno];
 			curpage = pageno;
 		}
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index fe75ead3501..728fb3c305d 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -811,6 +811,7 @@ SerialInit(void)
 	 * Set up SLRU management of the pg_serial data.
 	 */
 	SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
+	SerialSlruCtl->IoErrorMsg = TransactionIdIoErrorMsg;
 	SimpleLruInit(SerialSlruCtl, "serializable",
 				  serializable_buffers, 0, "pg_serial",
 				  LWTRANCHE_SERIAL_BUFFER, LWTRANCHE_SERIAL_SLRU,
@@ -930,7 +931,7 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
 	else
 	{
 		LWLockAcquire(lock, LW_EXCLUSIVE);
-		slotno = SimpleLruReadPage(SerialSlruCtl, targetPage, true, xid);
+		slotno = SimpleLruReadPage(SerialSlruCtl, targetPage, true, &xid);
 	}
 
 	SerialValue(slotno, xid) = minConflictCommitSeqNo;
@@ -974,7 +975,7 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
 	 * but will return with that lock held, which must then be released.
 	 */
 	slotno = SimpleLruReadPage_ReadOnly(SerialSlruCtl,
-										SerialPage(xid), xid);
+										SerialPage(xid), &xid);
 	val = SerialValue(slotno, xid);
 	LWLockRelease(SimpleLruGetBankLock(SerialSlruCtl, SerialPage(xid)));
 	return val;
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index 4cb8f478fce..67247365d16 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -13,6 +13,7 @@
 #ifndef SLRU_H
 #define SLRU_H
 
+#include "access/transam.h"
 #include "access/xlogdefs.h"
 #include "storage/lwlock.h"
 #include "storage/sync.h"
@@ -146,10 +147,31 @@ typedef struct SlruCtlData
 	 * it's always the same, it doesn't need to be in shared memory.
 	 */
 	char		Dir[64];
+
+	/*
+	 * Callback for creating an I/O error message.
+	 *
+	 * The opaque_data argument here is the same one that is passed to the
+	 * SimpleLruReadPage* calls.
+	 */
+	char	   *(*IoErrorMsg)(const void *opaque_data);
 } SlruCtlData;
 
 typedef SlruCtlData *SlruCtl;
 
+/*
+ * Historically, this module was designed for handling transaction IDs,
+ * therefore this is the most common use case. Thus, make it publicly available.
+ */
+static inline char *
+TransactionIdIoErrorMsg(const void *opaque_data)
+{
+	TransactionId xid = opaque_data ? (*(TransactionId *) opaque_data) :
+									  InvalidTransactionId;
+
+	return psprintf("could not access status of transaction %u", xid);
+}
+
 /*
  * Get the SLRU bank lock for given SlruCtl and the pageno.
  *
@@ -174,9 +196,9 @@ extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
 extern int	SimpleLruZeroPage(SlruCtl ctl, int64 pageno);
 extern void SimpleLruZeroAndWritePage(SlruCtl ctl, int64 pageno);
 extern int	SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
-							  TransactionId xid);
+							  const void *opaque_data);
 extern int	SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno,
-									   TransactionId xid);
+									   const void *opaque_data);
 extern void SimpleLruWritePage(SlruCtl ctl, int slotno);
 extern void SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied);
 #ifdef USE_ASSERT_CHECKING
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index 4dc74e19620..59ce4900173 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -100,7 +100,7 @@ test_slru_page_read(PG_FUNCTION_ARGS)
 	/* find page in buffers, reading it if necessary */
 	LWLockAcquire(lock, LW_EXCLUSIVE);
 	slotno = SimpleLruReadPage(TestSlruCtl, pageno,
-							   write_ok, InvalidTransactionId);
+							   write_ok, NULL);
 	data = (char *) TestSlruCtl->shared->page_buffer[slotno];
 	LWLockRelease(lock);
 
@@ -118,7 +118,7 @@ test_slru_page_readonly(PG_FUNCTION_ARGS)
 	/* find page in buffers, reading it if necessary */
 	slotno = SimpleLruReadPage_ReadOnly(TestSlruCtl,
 										pageno,
-										InvalidTransactionId);
+										NULL);
 	Assert(LWLockHeldByMe(lock));
 	data = (char *) TestSlruCtl->shared->page_buffer[slotno];
 	LWLockRelease(lock);
@@ -245,6 +245,7 @@ test_slru_shmem_startup(void)
 	}
 
 	TestSlruCtl->PagePrecedes = test_slru_page_precedes_logically;
+	TestSlruCtl->IoErrorMsg = TransactionIdIoErrorMsg;
 	SimpleLruInit(TestSlruCtl, "TestSLRU",
 				  NUM_TEST_BUFFERS, 0, slru_dir_name,
 				  test_buffer_tranche_id, test_tranche_id, SYNC_HANDLER_NONE,
-- 
2.50.1 (Apple Git-155)

