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
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