Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c3de0f9

Browse files
committed
Fix failures with incorrect epoch handling for 2PC files at recovery
At the beginning of recovery, an orphaned two-phase file in an epoch different than the one defined in the checkpoint record could not be removed based on the assumptions that AdjustToFullTransactionId() relies on, assuming that all files would be either from the current epoch or from the previous epoch. If the checkpoint epoch was 0 while the 2PC file was orphaned and in the future, AdjustToFullTransactionId() would underflow the epoch used to build the 2PC file path. In non-assert builds, this would create a WARNING message referring to a 2PC file with an epoch of "FFFFFFFF" (or UINT32_MAX), as an effect of the underflow calculation, leaving the orphaned file around. Some tests are added with dummy 2PC files in the past and the future, checking that these are properly removed. Issue introduced by 5a1dfde, that has switched two-phase state files to use FullTransactionIds. Reported-by: Vitaly Davydov Author: Michael Paquier Reviewed-by: Vitaly Davydov Discussion: https://postgr.es/m/13b5b6-676c3080-4d-531db900@47931709 Backpatch-through: 17
1 parent 03c46e1 commit c3de0f9

File tree

2 files changed

+150
-51
lines changed

2 files changed

+150
-51
lines changed

src/backend/access/transam/twophase.c

+116-51
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,13 @@ static void ProcessRecords(char *bufptr, TransactionId xid,
221221
static void RemoveGXact(GlobalTransaction gxact);
222222

223223
static void XlogReadTwoPhaseData(XLogRecPtr lsn, char **buf, int *len);
224-
static char *ProcessTwoPhaseBuffer(TransactionId xid,
224+
static char *ProcessTwoPhaseBuffer(FullTransactionId xid,
225225
XLogRecPtr prepare_start_lsn,
226226
bool fromdisk, bool setParent, bool setNextXid);
227227
static void MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid,
228228
const char *gid, TimestampTz prepared_at, Oid owner,
229229
Oid databaseid);
230-
static void RemoveTwoPhaseFile(TransactionId xid, bool giveWarning);
230+
static void RemoveTwoPhaseFile(FullTransactionId fxid, bool giveWarning);
231231
static void RecreateTwoPhaseFile(TransactionId xid, void *content, int len);
232232

233233
/*
@@ -927,41 +927,26 @@ TwoPhaseGetDummyProc(TransactionId xid, bool lock_held)
927927
/************************************************************************/
928928

929929
/*
930-
* Compute the FullTransactionId for the given TransactionId.
931-
*
932-
* The wrap logic is safe here because the span of active xids cannot exceed one
933-
* epoch at any given time.
930+
* Compute FullTransactionId for the given TransactionId, using the current
931+
* epoch.
934932
*/
935933
static inline FullTransactionId
936-
AdjustToFullTransactionId(TransactionId xid)
934+
FullTransactionIdFromCurrentEpoch(TransactionId xid)
937935
{
936+
FullTransactionId fxid;
938937
FullTransactionId nextFullXid;
939-
TransactionId nextXid;
940938
uint32 epoch;
941939

942-
Assert(TransactionIdIsValid(xid));
943-
944-
LWLockAcquire(XidGenLock, LW_SHARED);
945-
nextFullXid = TransamVariables->nextXid;
946-
LWLockRelease(XidGenLock);
947-
948-
nextXid = XidFromFullTransactionId(nextFullXid);
940+
nextFullXid = ReadNextFullTransactionId();
949941
epoch = EpochFromFullTransactionId(nextFullXid);
950-
if (unlikely(xid > nextXid))
951-
{
952-
/* Wraparound occurred, must be from a prev epoch. */
953-
Assert(epoch > 0);
954-
epoch--;
955-
}
956942

957-
return FullTransactionIdFromEpochAndXid(epoch, xid);
943+
fxid = FullTransactionIdFromEpochAndXid(epoch, xid);
944+
return fxid;
958945
}
959946

960947
static inline int
961-
TwoPhaseFilePath(char *path, TransactionId xid)
948+
TwoPhaseFilePath(char *path, FullTransactionId fxid)
962949
{
963-
FullTransactionId fxid = AdjustToFullTransactionId(xid);
964-
965950
return snprintf(path, MAXPGPATH, TWOPHASE_DIR "/%08X%08X",
966951
EpochFromFullTransactionId(fxid),
967952
XidFromFullTransactionId(fxid));
@@ -1297,7 +1282,8 @@ RegisterTwoPhaseRecord(TwoPhaseRmgrId rmid, uint16 info,
12971282
* If it looks OK (has a valid magic number and CRC), return the palloc'd
12981283
* contents of the file, issuing an error when finding corrupted data. If
12991284
* missing_ok is true, which indicates that missing files can be safely
1300-
* ignored, then return NULL. This state can be reached when doing recovery.
1285+
* ignored, then return NULL. This state can be reached when doing recovery
1286+
* after discarding two-phase files from other epochs.
13011287
*/
13021288
static char *
13031289
ReadTwoPhaseFile(TransactionId xid, bool missing_ok)
@@ -1311,8 +1297,10 @@ ReadTwoPhaseFile(TransactionId xid, bool missing_ok)
13111297
pg_crc32c calc_crc,
13121298
file_crc;
13131299
int r;
1300+
FullTransactionId fxid;
13141301

1315-
TwoPhaseFilePath(path, xid);
1302+
fxid = FullTransactionIdFromCurrentEpoch(xid);
1303+
TwoPhaseFilePath(path, fxid);
13161304

13171305
fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
13181306
if (fd < 0)
@@ -1677,10 +1665,16 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
16771665
AtEOXact_PgStat(isCommit, false);
16781666

16791667
/*
1680-
* And now we can clean up any files we may have left.
1668+
* And now we can clean up any files we may have left. These should be
1669+
* from the current epoch.
16811670
*/
16821671
if (ondisk)
1683-
RemoveTwoPhaseFile(xid, true);
1672+
{
1673+
FullTransactionId fxid;
1674+
1675+
fxid = FullTransactionIdFromCurrentEpoch(xid);
1676+
RemoveTwoPhaseFile(fxid, true);
1677+
}
16841678

16851679
MyLockedGxact = NULL;
16861680

@@ -1719,13 +1713,17 @@ ProcessRecords(char *bufptr, TransactionId xid,
17191713
*
17201714
* If giveWarning is false, do not complain about file-not-present;
17211715
* this is an expected case during WAL replay.
1716+
*
1717+
* This routine is used at early stages at recovery where future and
1718+
* past orphaned files are checked, hence the FullTransactionId to build
1719+
* a complete file name fit for the removal.
17221720
*/
17231721
static void
1724-
RemoveTwoPhaseFile(TransactionId xid, bool giveWarning)
1722+
RemoveTwoPhaseFile(FullTransactionId fxid, bool giveWarning)
17251723
{
17261724
char path[MAXPGPATH];
17271725

1728-
TwoPhaseFilePath(path, xid);
1726+
TwoPhaseFilePath(path, fxid);
17291727
if (unlink(path))
17301728
if (errno != ENOENT || giveWarning)
17311729
ereport(WARNING,
@@ -1745,13 +1743,16 @@ RecreateTwoPhaseFile(TransactionId xid, void *content, int len)
17451743
char path[MAXPGPATH];
17461744
pg_crc32c statefile_crc;
17471745
int fd;
1746+
FullTransactionId fxid;
17481747

17491748
/* Recompute CRC */
17501749
INIT_CRC32C(statefile_crc);
17511750
COMP_CRC32C(statefile_crc, content, len);
17521751
FIN_CRC32C(statefile_crc);
17531752

1754-
TwoPhaseFilePath(path, xid);
1753+
/* Use current epoch */
1754+
fxid = FullTransactionIdFromCurrentEpoch(xid);
1755+
TwoPhaseFilePath(path, fxid);
17551756

17561757
fd = OpenTransientFile(path,
17571758
O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY);
@@ -1899,7 +1900,9 @@ CheckPointTwoPhase(XLogRecPtr redo_horizon)
18991900
* Scan pg_twophase and fill TwoPhaseState depending on the on-disk data.
19001901
* This is called once at the beginning of recovery, saving any extra
19011902
* lookups in the future. Two-phase files that are newer than the
1902-
* minimum XID horizon are discarded on the way.
1903+
* minimum XID horizon are discarded on the way. Two-phase files with
1904+
* an epoch older or newer than the current checkpoint's record epoch
1905+
* are also discarded.
19031906
*/
19041907
void
19051908
restoreTwoPhaseData(void)
@@ -1914,14 +1917,11 @@ restoreTwoPhaseData(void)
19141917
if (strlen(clde->d_name) == 16 &&
19151918
strspn(clde->d_name, "0123456789ABCDEF") == 16)
19161919
{
1917-
TransactionId xid;
19181920
FullTransactionId fxid;
19191921
char *buf;
19201922

19211923
fxid = FullTransactionIdFromU64(strtou64(clde->d_name, NULL, 16));
1922-
xid = XidFromFullTransactionId(fxid);
1923-
1924-
buf = ProcessTwoPhaseBuffer(xid, InvalidXLogRecPtr,
1924+
buf = ProcessTwoPhaseBuffer(fxid, InvalidXLogRecPtr,
19251925
true, false, false);
19261926
if (buf == NULL)
19271927
continue;
@@ -1972,6 +1972,7 @@ PrescanPreparedTransactions(TransactionId **xids_p, int *nxids_p)
19721972
TransactionId origNextXid = XidFromFullTransactionId(nextXid);
19731973
TransactionId result = origNextXid;
19741974
TransactionId *xids = NULL;
1975+
uint32 epoch = EpochFromFullTransactionId(nextXid);
19751976
int nxids = 0;
19761977
int allocsize = 0;
19771978
int i;
@@ -1980,14 +1981,20 @@ PrescanPreparedTransactions(TransactionId **xids_p, int *nxids_p)
19801981
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
19811982
{
19821983
TransactionId xid;
1984+
FullTransactionId fxid;
19831985
char *buf;
19841986
GlobalTransaction gxact = TwoPhaseState->prepXacts[i];
19851987

19861988
Assert(gxact->inredo);
19871989

19881990
xid = gxact->xid;
19891991

1990-
buf = ProcessTwoPhaseBuffer(xid,
1992+
/*
1993+
* All two-phase files with past and future epoch in pg_twophase are
1994+
* gone at this point, so we're OK to rely on only the current epoch.
1995+
*/
1996+
fxid = FullTransactionIdFromEpochAndXid(epoch, xid);
1997+
buf = ProcessTwoPhaseBuffer(fxid,
19911998
gxact->prepare_start_lsn,
19921999
gxact->ondisk, false, true);
19932000

@@ -2049,19 +2056,31 @@ void
20492056
StandbyRecoverPreparedTransactions(void)
20502057
{
20512058
int i;
2059+
uint32 epoch;
2060+
FullTransactionId nextFullXid;
2061+
2062+
/* get current epoch */
2063+
nextFullXid = ReadNextFullTransactionId();
2064+
epoch = EpochFromFullTransactionId(nextFullXid);
20522065

20532066
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
20542067
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
20552068
{
20562069
TransactionId xid;
2070+
FullTransactionId fxid;
20572071
char *buf;
20582072
GlobalTransaction gxact = TwoPhaseState->prepXacts[i];
20592073

20602074
Assert(gxact->inredo);
20612075

20622076
xid = gxact->xid;
20632077

2064-
buf = ProcessTwoPhaseBuffer(xid,
2078+
/*
2079+
* At this stage, we're OK to work with the current epoch as all past
2080+
* and future files have been already discarded.
2081+
*/
2082+
fxid = FullTransactionIdFromEpochAndXid(epoch, xid);
2083+
buf = ProcessTwoPhaseBuffer(fxid,
20652084
gxact->prepare_start_lsn,
20662085
gxact->ondisk, true, false);
20672086
if (buf != NULL)
@@ -2090,18 +2109,29 @@ void
20902109
RecoverPreparedTransactions(void)
20912110
{
20922111
int i;
2112+
uint32 epoch;
2113+
FullTransactionId nextFullXid;
2114+
2115+
/* get current epoch */
2116+
nextFullXid = ReadNextFullTransactionId();
2117+
epoch = EpochFromFullTransactionId(nextFullXid);
20932118

20942119
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
20952120
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
20962121
{
20972122
TransactionId xid;
2123+
FullTransactionId fxid;
20982124
char *buf;
20992125
GlobalTransaction gxact = TwoPhaseState->prepXacts[i];
21002126
char *bufptr;
21012127
TwoPhaseFileHeader *hdr;
21022128
TransactionId *subxids;
21032129
const char *gid;
21042130

2131+
/*
2132+
* At this stage, we're OK to work with the current epoch as all past
2133+
* and future files have been already discarded.
2134+
*/
21052135
xid = gxact->xid;
21062136

21072137
/*
@@ -2113,7 +2143,8 @@ RecoverPreparedTransactions(void)
21132143
* SubTransSetParent has been set before, if the prepared transaction
21142144
* generated xid assignment records.
21152145
*/
2116-
buf = ProcessTwoPhaseBuffer(xid,
2146+
fxid = FullTransactionIdFromEpochAndXid(epoch, xid);
2147+
buf = ProcessTwoPhaseBuffer(fxid,
21172148
gxact->prepare_start_lsn,
21182149
gxact->ondisk, true, false);
21192150
if (buf == NULL)
@@ -2181,7 +2212,7 @@ RecoverPreparedTransactions(void)
21812212
/*
21822213
* ProcessTwoPhaseBuffer
21832214
*
2184-
* Given a transaction id, read it either from disk or read it directly
2215+
* Given a FullTransactionId, read it either from disk or read it directly
21852216
* via shmem xlog record pointer using the provided "prepare_start_lsn".
21862217
*
21872218
* If setParent is true, set up subtransaction parent linkages.
@@ -2190,32 +2221,35 @@ RecoverPreparedTransactions(void)
21902221
* value scanned.
21912222
*/
21922223
static char *
2193-
ProcessTwoPhaseBuffer(TransactionId xid,
2224+
ProcessTwoPhaseBuffer(FullTransactionId fxid,
21942225
XLogRecPtr prepare_start_lsn,
21952226
bool fromdisk,
21962227
bool setParent, bool setNextXid)
21972228
{
21982229
FullTransactionId nextXid = TransamVariables->nextXid;
2199-
TransactionId origNextXid = XidFromFullTransactionId(nextXid);
22002230
TransactionId *subxids;
22012231
char *buf;
22022232
TwoPhaseFileHeader *hdr;
22032233
int i;
2234+
TransactionId xid = XidFromFullTransactionId(fxid);
22042235

22052236
Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
22062237

22072238
if (!fromdisk)
22082239
Assert(prepare_start_lsn != InvalidXLogRecPtr);
22092240

2210-
/* Reject XID if too new */
2211-
if (TransactionIdFollowsOrEquals(xid, origNextXid))
2241+
/*
2242+
* Reject full XID if too new. Note that this discards files from future
2243+
* epochs.
2244+
*/
2245+
if (FullTransactionIdFollowsOrEquals(fxid, nextXid))
22122246
{
22132247
if (fromdisk)
22142248
{
22152249
ereport(WARNING,
2216-
(errmsg("removing future two-phase state file for transaction %u",
2217-
xid)));
2218-
RemoveTwoPhaseFile(xid, true);
2250+
(errmsg("removing future two-phase state file of epoch %u for transaction %u",
2251+
EpochFromFullTransactionId(fxid), xid)));
2252+
RemoveTwoPhaseFile(fxid, true);
22192253
}
22202254
else
22212255
{
@@ -2227,6 +2261,26 @@ ProcessTwoPhaseBuffer(TransactionId xid,
22272261
return NULL;
22282262
}
22292263

2264+
/* Discard files from past epochs */
2265+
if (EpochFromFullTransactionId(fxid) < EpochFromFullTransactionId(nextXid))
2266+
{
2267+
if (fromdisk)
2268+
{
2269+
ereport(WARNING,
2270+
(errmsg("removing past two-phase state file of epoch %u for transaction %u",
2271+
EpochFromFullTransactionId(fxid), xid)));
2272+
RemoveTwoPhaseFile(fxid, true);
2273+
}
2274+
else
2275+
{
2276+
ereport(WARNING,
2277+
(errmsg("removing past two-phase state from memory for transaction %u",
2278+
xid)));
2279+
PrepareRedoRemove(xid, true);
2280+
}
2281+
return NULL;
2282+
}
2283+
22302284
/* Already processed? */
22312285
if (TransactionIdDidCommit(xid) || TransactionIdDidAbort(xid))
22322286
{
@@ -2235,7 +2289,7 @@ ProcessTwoPhaseBuffer(TransactionId xid,
22352289
ereport(WARNING,
22362290
(errmsg("removing stale two-phase state file for transaction %u",
22372291
xid)));
2238-
RemoveTwoPhaseFile(xid, true);
2292+
RemoveTwoPhaseFile(fxid, true);
22392293
}
22402294
else
22412295
{
@@ -2521,8 +2575,11 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn,
25212575
if (!XLogRecPtrIsInvalid(start_lsn))
25222576
{
25232577
char path[MAXPGPATH];
2578+
FullTransactionId fxid;
25242579

2525-
TwoPhaseFilePath(path, hdr->xid);
2580+
/* Use current epoch */
2581+
fxid = FullTransactionIdFromCurrentEpoch(hdr->xid);
2582+
TwoPhaseFilePath(path, fxid);
25262583

25272584
if (access(path, F_OK) == 0)
25282585
{
@@ -2617,7 +2674,15 @@ PrepareRedoRemove(TransactionId xid, bool giveWarning)
26172674
*/
26182675
elog(DEBUG2, "removing 2PC data for transaction %u", xid);
26192676
if (gxact->ondisk)
2620-
RemoveTwoPhaseFile(xid, giveWarning);
2677+
{
2678+
FullTransactionId fxid;
2679+
2680+
/*
2681+
* We should deal with a file at the current epoch here.
2682+
*/
2683+
fxid = FullTransactionIdFromCurrentEpoch(xid);
2684+
RemoveTwoPhaseFile(fxid, giveWarning);
2685+
}
26212686
RemoveGXact(gxact);
26222687
}
26232688

0 commit comments

Comments
 (0)