From 77ffb554871482a1372bb16e7a76f15410f03e23 Mon Sep 17 00:00:00 2001
From: Atish <125atishprak@gmail.com>
Date: Sat, 7 Nov 2026 01:52:24 +0530
Subject: [PATCH] Fix assertion failure when truncating an aborted spilled
 transaction

ReorderBufferSerializeTXN() marked a transaction as serialized even when
it had no changes to write.  This happens to a subtransaction that has an
XID but no decoded changes, such as one that only locked a row, when its
parent is spilled to disk.  Such a subtransaction has no valid final_lsn
until its commit or abort is decoded.

If the parent is later evicted again and is found to have aborted before
its abort record is decoded, ReorderBufferCheckAndTruncateAbortedTXN()
truncates it and ReorderBufferRestoreCleanup() fails its assertion on the
subtransaction's final_lsn.

Mark the transaction as serialized only if something was written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---
 src/backend/replication/logical/reorderbuffer.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 4684398531..2b9624e6e0 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -4216,7 +4216,16 @@ ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
 	Assert(spilled == txn->nentries_mem);
 	Assert(dlist_is_empty(&txn->changes));
 	txn->nentries_mem = 0;
-	txn->txn_flags |= RBTXN_IS_SERIALIZED;
+
+	/*
+	 * Mark the transaction as serialized only if we wrote something.  A
+	 * subtransaction without any decoded changes gets here too, and it has no
+	 * valid final_lsn until its commit or abort is decoded, which
+	 * ReorderBufferRestoreCleanup() would need if the transaction were found
+	 * to be aborted before that.
+	 */
+	if (spilled)
+		txn->txn_flags |= RBTXN_IS_SERIALIZED;
 
 	if (fd != -1)
 		CloseTransientFile(fd);
-- 
2.53.0

