From 326619b374a8e5c7bc4a34f972d75ee8e27e2c5c Mon Sep 17 00:00:00 2001
From: Zhijie Hou <houzj.fnst@fujitsu.com>
Date: Wed, 2 Sep 2026 00:44:31 +0800
Subject: [PATCH vM1] Report WAL insert position in primary status update
 messages

The retain_dead_tuples feature waits for concurrent transactions to be applied
before advancing conflict_detection_slot.xmin. It fetches the WAL write position
from the publisher and waits until the apply remote position passes it. This is
necessary to retain dead tuples, commit timestamps, and origins for conflict
detection (update_deleted, update_origin_differs, delete_origin_differs) when
applying those concurrent transactions.

However, when asynchronous commit is enabled on the publisher, concurrent
transactions do not update the WAL write position immediately. As a result, the
received WAL position may be earlier than intended (e.g., it may not reflect the
latest committed transaction). This can cause conflict_detection_slot.xmin to
advance prematurely, losing dead tuples needed for conflict detection when
applying subsequent asynchronously committed transactions.

Fix this by reporting the end of the last inserted WAL record instead, ensuring
that the insert position covers every transaction that has already committed
and has a commit timestamp.
---
 src/backend/replication/logical/worker.c |  6 +++---
 src/backend/replication/walsender.c      | 21 ++++++++++++++++++++-
 2 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 0efed3a6f77..455099f68e6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -158,8 +158,8 @@
  *
  * - RDT_REQUEST_PUBLISHER_STATUS:
  *   Send a message to the walsender requesting the publisher status, which
- *   includes the latest WAL write position and information about transactions
- *   that are in the commit phase.
+ *   includes the latest WAL insert position and information about
+ *   transactions that are in the commit phase.
  *
  * - RDT_WAIT_FOR_PUBLISHER_STATUS:
  *   Wait for the status from the walsender. After receiving the first status,
@@ -417,7 +417,7 @@ typedef enum
 typedef struct RetainDeadTuplesData
 {
 	RetainDeadTuplesPhase phase;	/* current phase */
-	XLogRecPtr	remote_lsn;		/* WAL write position on the publisher */
+	XLogRecPtr	remote_lsn;		/* WAL insert position on the publisher */
 
 	/*
 	 * Oldest transaction ID that was in the commit phase on the publisher.
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index c65dd324325..2cdc149621c 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -2895,7 +2895,26 @@ ProcessStandbyPSRequestMessage(void)
 	nextFullXid = ReadNextFullTransactionId();
 	fullOldestXidInCommit = FullTransactionIdFromAllowableAt(nextFullXid,
 															 oldestXidInCommit);
-	lsn = GetXLogWriteRecPtr();
+
+	/*
+	 * Report the end of the last inserted WAL record rather than the WAL
+	 * write position.  A transaction that commits asynchronously (with
+	 * synchronous_commit = off) clears DELAY_CHKPT_IN_COMMIT without flushing
+	 * its commit record, so it is visible to neither the in-commit scan above
+	 * nor a write position that has not yet reached its commit record.  The
+	 * subscriber waits until it has applied and flushed up to the reported
+	 * position before advancing its non-removable transaction ID, so the
+	 * reported position must cover every transaction that can already own a
+	 * commit timestamp.  The insert position guarantees that, because a
+	 * commit timestamp is assigned only after the commit record has been
+	 * inserted.
+	 *
+	 * GetXLogInsertEndRecPtr() is used rather than GetXLogInsertRecPtr()
+	 * because the latter can return a position past the page header when the
+	 * last record ends at a page boundary, which can never match a record end
+	 * and would needlessly stall the subscriber's wait.
+	 */
+	lsn = GetXLogInsertEndRecPtr();
 
 	elog(DEBUG2, "sending primary status");
 
-- 
2.43.0

