From 33d6b7f2559dcb312de4cdcc3d75c93dad7dfe47 Mon Sep 17 00:00:00 2001
From: Rui Zhao <zhaorui126@gmail.com>
Date: Sun, 13 Sep 2026 00:00:27 +0800
Subject: [PATCH v5 1/3] Wait for the transactions of an initial decoding
 snapshot to finish

SnapBuildInitialSnapshot() converts the snapshot builder's list of
committed transactions into a regular MVCC snapshot, which is then used
with HeapTupleSatisfiesMVCC(). That function consults CLOG about the
transactions the snapshot takes as not running, so each of them has to
have finished committing before the snapshot is handed out: the commit
record is written first, CLOG is updated afterwards, and the transaction
stays in the procarray until after that.

Read the set of running transactions once, and wait on the transaction
lock of those that are in the snapshot's list, as SnapBuildWaitSnapshot()
does in the same code path; the others have left the procarray and so
have updated CLOG. Historic snapshots built by SnapBuildBuildSnapshot()
need no such wait: they rely on the xip array for transactions between
xmin and xmax, and consult CLOG only for transactions below xmin, which
had left the procarray when the xl_running_xacts record that set xmin was
written.
---
 src/backend/replication/logical/snapbuild.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index de491ea0c4b..261f25a5cd7 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -517,8 +517,22 @@ SnapBuildInitialSnapshot(SnapBuild *builder)
 						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 						 errmsg("initial slot snapshot too large")));
 
-			newxip[newxcnt++] = xid;
+			newxip[newxcnt] = xid;
 		}
+		else
+		{
+			/*
+			 * The commit record of this transaction has been decoded, but the
+			 * commit itself may not have finished, if it's still in the process
+			 * of removing itself from the procarray or waiting for a synchronous
+			 * standby.  To avoid producing a snapshot that inconsistently shows
+			 * this transaction as committed, wait until it actually is.
+			 */
+			if (!RecoveryInProgress())
+				XactLockTableWait(xid, NULL, NULL, XLTW_None);
+		}
+
+		newxcnt++;
 
 		TransactionIdAdvance(xid);
 	}
-- 
2.47.3

