From 5cfdb02ef6d0697e5670356345fc89af78d1eeb9 Mon Sep 17 00:00:00 2001
From: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Date: Thu, 12 Mar 2026 14:29:21 +0100
Subject: Fix flushing record ending at page boundary

In 6eedb2a5fd, a call to XLogFlush(GetXLogInsertRecPtr()) has been added
to allow walsender to flush the latest WAL record. However, if the last
record is at the end of a page, GetXLogInsertRecPtr() will return the
start position for the next record, which will be located in the next
page, after the page header.

XLogInsert will complain with a 'xlog flush request 0/03604018 is not
satisfied --- flushed only to 0/03604000' error, as the flush request
tries to write WAL that hasn't been reserved yet.

This patch fixes the issue by introducing and using a
GetXLogInsertEndRecPtr() which stops at the page boundary, instead of
the beginning of the next page.
---
 src/backend/access/transam/xlog.c           | 18 ++++++++++++++++++
 src/backend/replication/logical/syncutils.c |  2 +-
 src/backend/replication/walsender.c         |  2 +-
 src/include/access/xlog.h                   |  1 +
 4 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b9b678f3722..9fd90636ee1 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -9595,6 +9595,24 @@ GetXLogInsertRecPtr(void)
 	return XLogBytePosToRecPtr(current_bytepos);
 }
 
+/*
+ * Like GetXLogInsertRecPtr, but if the position is at a page boundary, returns
+ * a pointer to the beginning of the page (ie. before page header), not to where
+ * the first xlog record on that page would go to.
+ */
+XLogRecPtr
+GetXLogInsertEndRecPtr(void)
+{
+	XLogCtlInsert *Insert = &XLogCtl->Insert;
+	uint64		current_bytepos;
+
+	SpinLockAcquire(&Insert->insertpos_lck);
+	current_bytepos = Insert->CurrBytePos;
+	SpinLockRelease(&Insert->insertpos_lck);
+
+	return XLogBytePosToEndRecPtr(current_bytepos);
+}
+
 /*
  * Get latest WAL write pointer
  */
diff --git a/src/backend/replication/logical/syncutils.c b/src/backend/replication/logical/syncutils.c
index ef61ca0437d..8c5da44d42e 100644
--- a/src/backend/replication/logical/syncutils.c
+++ b/src/backend/replication/logical/syncutils.c
@@ -62,7 +62,7 @@ FinishSyncWorker(void)
 	}
 
 	/* And flush all writes. */
-	XLogFlush(GetXLogWriteRecPtr());
+	XLogFlush(GetXLogInsertEndRecPtr());
 
 	if (am_sequencesync_worker())
 	{
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 79fc192b171..dd46de7bcd6 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1887,7 +1887,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * written, because walwriter has shut down already.
 		 */
 		if (got_STOPPING && !RecoveryInProgress())
-			XLogFlush(GetXLogInsertRecPtr());
+			XLogFlush(GetXLogInsertEndRecPtr());
 
 		/*
 		 * To avoid the scenario where standbys need to catch up to a newer
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index fdfb572467b..958f39edda4 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -238,6 +238,7 @@ extern bool RecoveryInProgress(void);
 extern RecoveryState GetRecoveryState(void);
 extern bool XLogInsertAllowed(void);
 extern XLogRecPtr GetXLogInsertRecPtr(void);
+extern XLogRecPtr GetXLogInsertEndRecPtr(void);
 extern XLogRecPtr GetXLogWriteRecPtr(void);
 
 extern uint64 GetSystemIdentifier(void);
-- 
2.53.0

