From afcb7662e45f2f52cc62a4b06e05604ac97f70b5 Mon Sep 17 00:00:00 2001
From: Ajin Cherian <ajinc@fast.au.fujitsu.com>
Date: Tue, 30 Mar 2021 00:16:35 -0400
Subject: [PATCH v3] Make sure a prepare is sent when decoder detects a
 concurrent abort.

While decoding a prepared transaction, and a concurrent abort of the transaction
being decoded is detected, the decoding is stopped. But this fix makes sure that
even if the decoding is aborted, the PREPARE is sent out. This ensures that
when the ROLLBACK PREPARED is eventually sent downstream, there is a corresponding
prepared transaction to rollback.
---
 doc/src/sgml/logicaldecoding.sgml               | 12 ++++++++----
 src/backend/replication/logical/reorderbuffer.c |  7 +++++++
 2 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index 80eb96d..e3f6008 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -545,12 +545,16 @@ CREATE TABLE another_catalog_table(data text) WITH (user_catalog_table = true);
      executed within that transaction. A transaction that is prepared for
      a two-phase commit using <command>PREPARE TRANSACTION</command> will
      also be decoded if the output plugin callbacks needed for decoding
-     them are provided. It is possible that the current transaction which
+     them are provided. It is possible that the current prepared transaction which
      is being decoded is aborted concurrently via a <command>ROLLBACK PREPARED</command>
      command. In that case, the logical decoding of this transaction will
-     be aborted too. We will skip all the changes of such a transaction once
-     the abort is detected and abort the transaction when we read WAL for
-     <command>ROLLBACK PREPARED</command>.
+     be aborted too. All the changes of such a transaction are skipped once
+     the abort is detected and the <function>prepare_cb</function> callback is invoked.
+     This could result in a prepared transaction with incomplete changes, in which case
+     the <literal>concurrent_abort</literal> field of the passed
+     <literal>ReorderBufferTXN</literal> struct is set.
+     This is done so that eventually when the <command>ROLLBACK PREPARED</command>
+     is decoded, there is a corresponding prepared transaction with a matching gid.
     </para>
 
     <note>
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 127f2c4..1442af1 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -2664,6 +2664,13 @@ ReorderBufferPrepare(ReorderBuffer *rb, TransactionId xid,
 
 	ReorderBufferReplay(txn, rb, xid, txn->final_lsn, txn->end_lsn,
 						txn->commit_time, txn->origin_id, txn->origin_lsn);
+
+	/*
+	 * If the transaction has been concurrently aborted, make sure we send
+	 * prepare here.
+	 */
+	if (txn->concurrent_abort)
+		rb->prepare(rb, txn, txn->final_lsn);
 }
 
 /*
-- 
1.8.3.1

