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

Commit 7e125b2

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 e358425 commit 7e125b2

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

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

1727-
TwoPhaseFilePath(path, xid);
1725+
TwoPhaseFilePath(path, fxid);
17281726
if (unlink(path))
17291727
if (errno != ENOENT || giveWarning)
17301728
ereport(WARNING,
@@ -1744,13 +1742,16 @@ RecreateTwoPhaseFile(TransactionId xid, void *content, int len)
17441742
char path[MAXPGPATH];
17451743
pg_crc32c statefile_crc;
17461744
int fd;
1745+
FullTransactionId fxid;
17471746

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

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

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

19201922
fxid = FullTransactionIdFromU64(strtou64(clde->d_name, NULL, 16));
1921-
xid = XidFromFullTransactionId(fxid);
1922-
1923-
buf = ProcessTwoPhaseBuffer(xid, InvalidXLogRecPtr,
1923+
buf = ProcessTwoPhaseBuffer(fxid, InvalidXLogRecPtr,
19241924
true, false, false);
19251925
if (buf == NULL)
19261926
continue;
@@ -1971,6 +1971,7 @@ PrescanPreparedTransactions(TransactionId **xids_p, int *nxids_p)
19711971
TransactionId origNextXid = XidFromFullTransactionId(nextXid);
19721972
TransactionId result = origNextXid;
19731973
TransactionId *xids = NULL;
1974+
uint32 epoch = EpochFromFullTransactionId(nextXid);
19741975
int nxids = 0;
19751976
int allocsize = 0;
19761977
int i;
@@ -1979,14 +1980,20 @@ PrescanPreparedTransactions(TransactionId **xids_p, int *nxids_p)
19791980
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
19801981
{
19811982
TransactionId xid;
1983+
FullTransactionId fxid;
19821984
char *buf;
19831985
GlobalTransaction gxact = TwoPhaseState->prepXacts[i];
19841986

19851987
Assert(gxact->inredo);
19861988

19871989
xid = gxact->xid;
19881990

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

@@ -2048,19 +2055,31 @@ void
20482055
StandbyRecoverPreparedTransactions(void)
20492056
{
20502057
int i;
2058+
uint32 epoch;
2059+
FullTransactionId nextFullXid;
2060+
2061+
/* get current epoch */
2062+
nextFullXid = ReadNextFullTransactionId();
2063+
epoch = EpochFromFullTransactionId(nextFullXid);
20512064

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

20592073
Assert(gxact->inredo);
20602074

20612075
xid = gxact->xid;
20622076

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

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

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

21062136
/*
@@ -2112,7 +2142,8 @@ RecoverPreparedTransactions(void)
21122142
* SubTransSetParent has been set before, if the prepared transaction
21132143
* generated xid assignment records.
21142144
*/
2115-
buf = ProcessTwoPhaseBuffer(xid,
2145+
fxid = FullTransactionIdFromEpochAndXid(epoch, xid);
2146+
buf = ProcessTwoPhaseBuffer(fxid,
21162147
gxact->prepare_start_lsn,
21172148
gxact->ondisk, true, false);
21182149
if (buf == NULL)
@@ -2180,7 +2211,7 @@ RecoverPreparedTransactions(void)
21802211
/*
21812212
* ProcessTwoPhaseBuffer
21822213
*
2183-
* Given a transaction id, read it either from disk or read it directly
2214+
* Given a FullTransactionId, read it either from disk or read it directly
21842215
* via shmem xlog record pointer using the provided "prepare_start_lsn".
21852216
*
21862217
* If setParent is true, set up subtransaction parent linkages.
@@ -2189,32 +2220,35 @@ RecoverPreparedTransactions(void)
21892220
* value scanned.
21902221
*/
21912222
static char *
2192-
ProcessTwoPhaseBuffer(TransactionId xid,
2223+
ProcessTwoPhaseBuffer(FullTransactionId fxid,
21932224
XLogRecPtr prepare_start_lsn,
21942225
bool fromdisk,
21952226
bool setParent, bool setNextXid)
21962227
{
21972228
FullTransactionId nextXid = TransamVariables->nextXid;
2198-
TransactionId origNextXid = XidFromFullTransactionId(nextXid);
21992229
TransactionId *subxids;
22002230
char *buf;
22012231
TwoPhaseFileHeader *hdr;
22022232
int i;
2233+
TransactionId xid = XidFromFullTransactionId(fxid);
22032234

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

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

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

2263+
/* Discard files from past epochs */
2264+
if (EpochFromFullTransactionId(fxid) < EpochFromFullTransactionId(nextXid))
2265+
{
2266+
if (fromdisk)
2267+
{
2268+
ereport(WARNING,
2269+
(errmsg("removing past two-phase state file of epoch %u for transaction %u",
2270+
EpochFromFullTransactionId(fxid), xid)));
2271+
RemoveTwoPhaseFile(fxid, true);
2272+
}
2273+
else
2274+
{
2275+
ereport(WARNING,
2276+
(errmsg("removing past two-phase state from memory for transaction %u",
2277+
xid)));
2278+
PrepareRedoRemove(xid, true);
2279+
}
2280+
return NULL;
2281+
}
2282+
22292283
/* Already processed? */
22302284
if (TransactionIdDidCommit(xid) || TransactionIdDidAbort(xid))
22312285
{
@@ -2234,7 +2288,7 @@ ProcessTwoPhaseBuffer(TransactionId xid,
22342288
ereport(WARNING,
22352289
(errmsg("removing stale two-phase state file for transaction %u",
22362290
xid)));
2237-
RemoveTwoPhaseFile(xid, true);
2291+
RemoveTwoPhaseFile(fxid, true);
22382292
}
22392293
else
22402294
{
@@ -2520,8 +2574,11 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn,
25202574
if (!XLogRecPtrIsInvalid(start_lsn))
25212575
{
25222576
char path[MAXPGPATH];
2577+
FullTransactionId fxid;
25232578

2524-
TwoPhaseFilePath(path, hdr->xid);
2579+
/* Use current epoch */
2580+
fxid = FullTransactionIdFromCurrentEpoch(hdr->xid);
2581+
TwoPhaseFilePath(path, fxid);
25252582

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

0 commit comments

Comments
 (0)