Skip to content

Commit 49e9281

Browse files
Rework handling of subtransactions in 2PC recovery
The bug fixed by 0874d4f caused us to question and rework the handling of subtransactions in 2PC during and at end of recovery. Patch adds checks and tests to ensure no further bugs. This effectively removes the temporary measure put in place by 546c13e. Author: Simon Riggs Reviewed-by: Tom Lane, Michael Paquier Discussion: http://postgr.es/m/CANP8+j+vvXmruL_i2buvdhMeVv5TQu0Hm2+C5N+kdVwHJuor8w@mail.gmail.com
1 parent 0352c15 commit 49e9281

File tree

7 files changed

+56
-55
lines changed

7 files changed

+56
-55
lines changed

src/backend/access/transam/subtrans.c

+22-10
Original file line numberDiff line numberDiff line change
@@ -68,32 +68,35 @@ static bool SubTransPagePrecedes(int page1, int page2);
6868

6969
/*
7070
* Record the parent of a subtransaction in the subtrans log.
71-
*
72-
* In some cases we may need to overwrite an existing value.
7371
*/
7472
void
75-
SubTransSetParent(TransactionId xid, TransactionId parent, bool overwriteOK)
73+
SubTransSetParent(TransactionId xid, TransactionId parent)
7674
{
7775
int pageno = TransactionIdToPage(xid);
7876
int entryno = TransactionIdToEntry(xid);
7977
int slotno;
8078
TransactionId *ptr;
8179

8280
Assert(TransactionIdIsValid(parent));
81+
Assert(TransactionIdFollows(xid, parent));
8382

8483
LWLockAcquire(SubtransControlLock, LW_EXCLUSIVE);
8584

8685
slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
8786
ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
8887
ptr += entryno;
8988

90-
/* Current state should be 0 */
91-
Assert(*ptr == InvalidTransactionId ||
92-
(*ptr == parent && overwriteOK));
93-
94-
*ptr = parent;
95-
96-
SubTransCtl->shared->page_dirty[slotno] = true;
89+
/*
90+
* It's possible we'll try to set the parent xid multiple times
91+
* but we shouldn't ever be changing the xid from one valid xid
92+
* to another valid xid, which would corrupt the data structure.
93+
*/
94+
if (*ptr != parent)
95+
{
96+
Assert(*ptr == InvalidTransactionId);
97+
*ptr = parent;
98+
SubTransCtl->shared->page_dirty[slotno] = true;
99+
}
97100

98101
LWLockRelease(SubtransControlLock);
99102
}
@@ -157,6 +160,15 @@ SubTransGetTopmostTransaction(TransactionId xid)
157160
if (TransactionIdPrecedes(parentXid, TransactionXmin))
158161
break;
159162
parentXid = SubTransGetParent(parentXid);
163+
164+
/*
165+
* By convention the parent xid gets allocated first, so should
166+
* always precede the child xid. Anything else points to a corrupted
167+
* data structure that could lead to an infinite loop, so exit.
168+
*/
169+
if (!TransactionIdPrecedes(parentXid, previousXid))
170+
elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
171+
previousXid, parentXid);
160172
}
161173

162174
Assert(TransactionIdIsValid(previousXid));

src/backend/access/transam/twophase.c

+28-39
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,7 @@ static void RemoveGXact(GlobalTransaction gxact);
221221
static void XlogReadTwoPhaseData(XLogRecPtr lsn, char **buf, int *len);
222222
static char *ProcessTwoPhaseBuffer(TransactionId xid,
223223
XLogRecPtr prepare_start_lsn,
224-
bool fromdisk, bool overwriteOK, bool setParent,
225-
bool setNextXid);
224+
bool fromdisk, bool setParent, bool setNextXid);
226225
static void MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid,
227226
const char *gid, TimestampTz prepared_at, Oid owner,
228227
Oid databaseid);
@@ -1743,8 +1742,7 @@ restoreTwoPhaseData(void)
17431742
xid = (TransactionId) strtoul(clde->d_name, NULL, 16);
17441743

17451744
buf = ProcessTwoPhaseBuffer(xid, InvalidXLogRecPtr,
1746-
true, false, false,
1747-
false);
1745+
true, false, false);
17481746
if (buf == NULL)
17491747
continue;
17501748

@@ -1804,8 +1802,7 @@ PrescanPreparedTransactions(TransactionId **xids_p, int *nxids_p)
18041802

18051803
buf = ProcessTwoPhaseBuffer(xid,
18061804
gxact->prepare_start_lsn,
1807-
gxact->ondisk, false, false,
1808-
true);
1805+
gxact->ondisk, false, true);
18091806

18101807
if (buf == NULL)
18111808
continue;
@@ -1858,12 +1855,12 @@ PrescanPreparedTransactions(TransactionId **xids_p, int *nxids_p)
18581855
* This is never called at the end of recovery - we use
18591856
* RecoverPreparedTransactions() at that point.
18601857
*
1861-
* Currently we simply call SubTransSetParent() for any subxids of prepared
1862-
* transactions. If overwriteOK is true, it's OK if some XIDs have already
1863-
* been marked in pg_subtrans.
1858+
* The lack of calls to SubTransSetParent() calls here is by design;
1859+
* those calls are made by RecoverPreparedTransactions() at the end of recovery
1860+
* for those xacts that need this.
18641861
*/
18651862
void
1866-
StandbyRecoverPreparedTransactions(bool overwriteOK)
1863+
StandbyRecoverPreparedTransactions(void)
18671864
{
18681865
int i;
18691866

@@ -1880,8 +1877,7 @@ StandbyRecoverPreparedTransactions(bool overwriteOK)
18801877

18811878
buf = ProcessTwoPhaseBuffer(xid,
18821879
gxact->prepare_start_lsn,
1883-
gxact->ondisk, overwriteOK, true,
1884-
false);
1880+
gxact->ondisk, false, false);
18851881
if (buf != NULL)
18861882
pfree(buf);
18871883
}
@@ -1895,6 +1891,13 @@ StandbyRecoverPreparedTransactions(bool overwriteOK)
18951891
* each prepared transaction (reacquire locks, etc).
18961892
*
18971893
* This is run during database startup.
1894+
*
1895+
* At the end of recovery the way we take snapshots will change. We now need
1896+
* to mark all running transactions with their full SubTransSetParent() info
1897+
* to allow normal snapshots to work correctly if snapshots overflow.
1898+
* We do this here because by definition prepared transactions are the only
1899+
* type of write transaction still running, so this is necessary and
1900+
* complete.
18981901
*/
18991902
void
19001903
RecoverPreparedTransactions(void)
@@ -1913,15 +1916,21 @@ RecoverPreparedTransactions(void)
19131916
TwoPhaseFileHeader *hdr;
19141917
TransactionId *subxids;
19151918
const char *gid;
1916-
bool overwriteOK = false;
1917-
int i;
19181919

19191920
xid = gxact->xid;
19201921

1922+
/*
1923+
* Reconstruct subtrans state for the transaction --- needed
1924+
* because pg_subtrans is not preserved over a restart. Note that
1925+
* we are linking all the subtransactions directly to the
1926+
* top-level XID; there may originally have been a more complex
1927+
* hierarchy, but there's no need to restore that exactly.
1928+
* It's possible that SubTransSetParent has been set before, if
1929+
* the prepared transaction generated xid assignment records.
1930+
*/
19211931
buf = ProcessTwoPhaseBuffer(xid,
19221932
gxact->prepare_start_lsn,
1923-
gxact->ondisk, false, false,
1924-
false);
1933+
gxact->ondisk, true, false);
19251934
if (buf == NULL)
19261935
continue;
19271936

@@ -1939,25 +1948,6 @@ RecoverPreparedTransactions(void)
19391948
bufptr += MAXALIGN(hdr->nabortrels * sizeof(RelFileNode));
19401949
bufptr += MAXALIGN(hdr->ninvalmsgs * sizeof(SharedInvalidationMessage));
19411950

1942-
/*
1943-
* It's possible that SubTransSetParent has been set before, if
1944-
* the prepared transaction generated xid assignment records. Test
1945-
* here must match one used in AssignTransactionId().
1946-
*/
1947-
if (InHotStandby && (hdr->nsubxacts >= PGPROC_MAX_CACHED_SUBXIDS ||
1948-
XLogLogicalInfoActive()))
1949-
overwriteOK = true;
1950-
1951-
/*
1952-
* Reconstruct subtrans state for the transaction --- needed
1953-
* because pg_subtrans is not preserved over a restart. Note that
1954-
* we are linking all the subtransactions directly to the
1955-
* top-level XID; there may originally have been a more complex
1956-
* hierarchy, but there's no need to restore that exactly.
1957-
*/
1958-
for (i = 0; i < hdr->nsubxacts; i++)
1959-
SubTransSetParent(subxids[i], xid, true);
1960-
19611951
/*
19621952
* Recreate its GXACT and dummy PGPROC. But, check whether
19631953
* it was added in redo and already has a shmem entry for
@@ -2006,16 +1996,15 @@ RecoverPreparedTransactions(void)
20061996
* Given a transaction id, read it either from disk or read it directly
20071997
* via shmem xlog record pointer using the provided "prepare_start_lsn".
20081998
*
2009-
* If setParent is true, then use the overwriteOK parameter to set up
2010-
* subtransaction parent linkages.
1999+
* If setParent is true, set up subtransaction parent linkages.
20112000
*
20122001
* If setNextXid is true, set ShmemVariableCache->nextXid to the newest
20132002
* value scanned.
20142003
*/
20152004
static char *
20162005
ProcessTwoPhaseBuffer(TransactionId xid,
20172006
XLogRecPtr prepare_start_lsn,
2018-
bool fromdisk, bool overwriteOK,
2007+
bool fromdisk,
20192008
bool setParent, bool setNextXid)
20202009
{
20212010
TransactionId origNextXid = ShmemVariableCache->nextXid;
@@ -2142,7 +2131,7 @@ ProcessTwoPhaseBuffer(TransactionId xid,
21422131
}
21432132

21442133
if (setParent)
2145-
SubTransSetParent(subxid, xid, overwriteOK);
2134+
SubTransSetParent(subxid, xid);
21462135
}
21472136

21482137
return buf;

src/backend/access/transam/xact.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ AssignTransactionId(TransactionState s)
559559
XactTopTransactionId = s->transactionId;
560560

561561
if (isSubXact)
562-
SubTransSetParent(s->transactionId, s->parent->transactionId, false);
562+
SubTransSetParent(s->transactionId, s->parent->transactionId);
563563

564564
/*
565565
* If it's a top-level transaction, the predicate locking system needs to

src/backend/access/transam/xlog.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -6930,7 +6930,7 @@ StartupXLOG(void)
69306930

69316931
ProcArrayApplyRecoveryInfo(&running);
69326932

6933-
StandbyRecoverPreparedTransactions(false);
6933+
StandbyRecoverPreparedTransactions();
69346934
}
69356935
}
69366936

@@ -9692,7 +9692,7 @@ xlog_redo(XLogReaderState *record)
96929692

96939693
ProcArrayApplyRecoveryInfo(&running);
96949694

9695-
StandbyRecoverPreparedTransactions(true);
9695+
StandbyRecoverPreparedTransactions();
96969696
}
96979697

96989698
/* ControlFile->checkPointCopy always tracks the latest ckpt XID */

src/backend/storage/ipc/procarray.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -943,7 +943,7 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
943943
* have attempted to SubTransSetParent().
944944
*/
945945
for (i = 0; i < nsubxids; i++)
946-
SubTransSetParent(subxids[i], topxid, false);
946+
SubTransSetParent(subxids[i], topxid);
947947

948948
/* KnownAssignedXids isn't maintained yet, so we're done for now */
949949
if (standbyState == STANDBY_INITIALIZED)

src/include/access/subtrans.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
/* Number of SLRU buffers to use for subtrans */
1515
#define NUM_SUBTRANS_BUFFERS 32
1616

17-
extern void SubTransSetParent(TransactionId xid, TransactionId parent, bool overwriteOK);
17+
extern void SubTransSetParent(TransactionId xid, TransactionId parent);
1818
extern TransactionId SubTransGetParent(TransactionId xid);
1919
extern TransactionId SubTransGetTopmostTransaction(TransactionId xid);
2020

src/include/access/twophase.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ extern bool StandbyTransactionIdIsPrepared(TransactionId xid);
4646

4747
extern TransactionId PrescanPreparedTransactions(TransactionId **xids_p,
4848
int *nxids_p);
49-
extern void StandbyRecoverPreparedTransactions(bool overwriteOK);
49+
extern void StandbyRecoverPreparedTransactions(void);
5050
extern void RecoverPreparedTransactions(void);
5151

5252
extern void CheckPointTwoPhase(XLogRecPtr redo_horizon);

0 commit comments

Comments
 (0)