v2-0001-Fix-a-2PC-transaction-maybe-recovered-twice_master.patch

application/octet-stream

Filename: v2-0001-Fix-a-2PC-transaction-maybe-recovered-twice_master.patch
Type: application/octet-stream
Part: 0
Message: Re: The same 2PC data maybe recovered twice

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v2-0001
Subject: Fix the bug of a 2PC transaction maybe recovered twice
File+
src/backend/access/transam/twophase.c 52 0
From e66b8c3285331537b09df577f4d487847e085a36 Mon Sep 17 00:00:00 2001
From: "suyu.cmj" <mengjuan.cmj@alibaba-inc.com>
Date: Mon, 17 Jul 2023 05:52:59 +0000
Subject: [PATCH] Fix the bug of a 2PC transaction maybe recovered twice

During recovery, a two-phase transaction should be restored either from
the disk file or from the WAL. However, a two-phase transaction maybe
restored from both way if we crashed after writing a 2PC file to disk,
but before the redo LSN has been updated to pg_control during doing a
checkpoint. We check if the 2PC file of the transaction being recovery
is already in pg_twophase/ when adding an entry from a WAL record, if a
consistent point has not been reached by recovery and we find a file on
disk, then do nothing because we know restoreTwoPhaseData() has done that
at the beginning of recovery. If a consistent point has been reached in
recovery and we find a file on disk while replaying a WAL record for the
same 2PC file, then fail. If there is no file in pg_twophase/ for the
record replayed, then add it to the array.
---
 src/backend/access/transam/twophase.c | 52 +++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 068e59bec0..9971a78d30 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -86,6 +86,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "access/xlogreader.h"
+#include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
@@ -2459,6 +2460,8 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn,
 	char	   *bufptr;
 	const char *gid;
 	GlobalTransaction gxact;
+	char		path[MAXPGPATH];
+	struct stat stat_buf;
 
 	Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
 	Assert(RecoveryInProgress());
@@ -2477,6 +2480,55 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn,
 	 * that it got added in the redo phase
 	 */
 
+	/*
+	 * During recovery, the two-phase data can be restored in two ways:
+	 * 1) restored from disk file when its xid < checkPoint.nextxid;
+	 * 2) restored from the WAL record when its prepare_start_lsn > checkPoint.redo.
+	 * A two-phase transaction can be restored from either disk file or WAL,
+	 * but can't both. However, a 2PC may satisfy above two conditions if
+	 * we crashed after writing a 2PC file to disk, but before the redo LSN
+	 * has been updated to pg_control during doing a checkpoint.
+	 *
+	 * We check if the 2PC file of the transaction being recovery is already
+	 * in pg_twophase/ when adding an entry from a WAL record, if a consistent
+	 * point has not been reached by recovery and we find a file on disk, then
+	 * do nothing because we know restoreTwoPhaseData() has done that at the
+	 * beginning of recovery. If a consistent point has been reached in recovery
+	 * and we find a file on disk while replaying a WAL record for the same 2PC
+	 * file, then fail. If there is no file in pg_twophase/ for the record
+	 * replayed, then add it to the array.
+	 */
+	if (!XLogRecPtrIsInvalid(start_lsn))
+	{
+#ifdef USE_ASSERT_CHECKING
+		int i;
+#endif
+		TwoPhaseFilePath(path, hdr->xid);
+
+		if (stat(path, &stat_buf) == 0)
+		{
+			Assert(S_ISREG(stat_buf.st_mode));
+
+			if (reachedConsistency)
+				ereport(FATAL,
+						(errmsg("found unexpected duplicate two-phase transaction:%u in pg_twophase, check for data correctness.",
+								hdr->xid)));
+			else
+			{
+				ereport(WARNING,
+						(errmsg("found duplicate two-phase transaction:%u in pg_twophase, skip adding this transaction.",
+								hdr->xid)));
+				return;
+			}
+		}
+
+#ifdef USE_ASSERT_CHECKING
+		/* cross-check for duplicates in array during replay */
+		for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
+			Assert(TwoPhaseState->prepXacts[i]->xid != hdr->xid);
+#endif
+	}
+
 	/* Get a free gxact from the freelist */
 	if (TwoPhaseState->freeGXacts == NULL)
 		ereport(ERROR,
-- 
2.19.1.6.gb485710b