Skip to content

Commit 3bd7f99

Browse files
committed
Track latest completed xid as a FullTransactionId.
The reason for doing so is that a subsequent commit will need that to avoid wraparound issues. As the subsequent change is large this was split out for easier review. The reason this is not a perfect straight-forward change is that we do not want track 64bit xids in the procarray or the WAL. Therefore we need to advance lastestCompletedXid in relation to 32 bit xids. The code for that is now centralized in MaintainLatestCompletedXid*. Author: Andres Freund Reviewed-By: Thomas Munro, Robert Haas, David Rowley Discussion: https://postgr.es/m/20200301083601.ews6hz5dduc3w2se@alap3.anarazel.de
1 parent fea10a6 commit 3bd7f99

File tree

4 files changed

+191
-29
lines changed

4 files changed

+191
-29
lines changed

src/backend/access/transam/varsup.c

+50
Original file line numberDiff line numberDiff line change
@@ -569,3 +569,53 @@ GetNewObjectId(void)
569569

570570
return result;
571571
}
572+
573+
574+
#ifdef USE_ASSERT_CHECKING
575+
576+
/*
577+
* Assert that xid is between [oldestXid, nextXid], which is the range we
578+
* expect XIDs coming from tables etc to be in.
579+
*
580+
* As ShmemVariableCache->oldestXid could change just after this call without
581+
* further precautions, and as a wrapped-around xid could again fall within
582+
* the valid range, this assertion can only detect if something is definitely
583+
* wrong, but not establish correctness.
584+
*
585+
* This intentionally does not expose a return value, to avoid code being
586+
* introduced that depends on the return value.
587+
*/
588+
void
589+
AssertTransactionIdInAllowableRange(TransactionId xid)
590+
{
591+
TransactionId oldest_xid;
592+
TransactionId next_xid;
593+
594+
Assert(TransactionIdIsValid(xid));
595+
596+
/* we may see bootstrap / frozen */
597+
if (!TransactionIdIsNormal(xid))
598+
return;
599+
600+
/*
601+
* We can't acquire XidGenLock, as this may be called with XidGenLock
602+
* already held (or with other locks that don't allow XidGenLock to be
603+
* nested). That's ok for our purposes though, since we already rely on
604+
* 32bit reads to be atomic. While nextXid is 64 bit, we only look at
605+
* the lower 32bit, so a skewed read doesn't hurt.
606+
*
607+
* There's no increased danger of falling outside [oldest, next] by
608+
* accessing them without a lock. xid needs to have been created with
609+
* GetNewTransactionId() in the originating session, and the locks there
610+
* pair with the memory barrier below. We do however accept xid to be <=
611+
* to next_xid, instead of just <, as xid could be from the procarray,
612+
* before we see the updated nextXid value.
613+
*/
614+
pg_memory_barrier();
615+
oldest_xid = ShmemVariableCache->oldestXid;
616+
next_xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
617+
618+
Assert(TransactionIdFollowsOrEquals(xid, oldest_xid) ||
619+
TransactionIdPrecedesOrEquals(xid, next_xid));
620+
}
621+
#endif

src/backend/access/transam/xlog.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -7865,8 +7865,8 @@ StartupXLOG(void)
78657865

78667866
/* also initialize latestCompletedXid, to nextXid - 1 */
78677867
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
7868-
ShmemVariableCache->latestCompletedXid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
7869-
TransactionIdRetreat(ShmemVariableCache->latestCompletedXid);
7868+
ShmemVariableCache->latestCompletedXid = ShmemVariableCache->nextXid;
7869+
FullTransactionIdRetreat(&ShmemVariableCache->latestCompletedXid);
78707870
LWLockRelease(ProcArrayLock);
78717871

78727872
/*

src/backend/storage/ipc/procarray.c

+104-25
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ static void KnownAssignedXidsReset(void);
175175
static inline void ProcArrayEndTransactionInternal(PGPROC *proc,
176176
PGXACT *pgxact, TransactionId latestXid);
177177
static void ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid);
178+
static void MaintainLatestCompletedXid(TransactionId latestXid);
179+
static void MaintainLatestCompletedXidRecovery(TransactionId latestXid);
180+
181+
static inline FullTransactionId FullXidRelativeTo(FullTransactionId rel,
182+
TransactionId xid);
178183

179184
/*
180185
* Report shared-memory space needed by CreateSharedProcArray.
@@ -349,9 +354,7 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
349354
Assert(TransactionIdIsValid(allPgXact[proc->pgprocno].xid));
350355

351356
/* Advance global latestCompletedXid while holding the lock */
352-
if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
353-
latestXid))
354-
ShmemVariableCache->latestCompletedXid = latestXid;
357+
MaintainLatestCompletedXid(latestXid);
355358
}
356359
else
357360
{
@@ -464,9 +467,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact,
464467
pgxact->overflowed = false;
465468

466469
/* Also advance global latestCompletedXid while holding the lock */
467-
if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
468-
latestXid))
469-
ShmemVariableCache->latestCompletedXid = latestXid;
470+
MaintainLatestCompletedXid(latestXid);
470471
}
471472

472473
/*
@@ -621,6 +622,59 @@ ProcArrayClearTransaction(PGPROC *proc)
621622
pgxact->overflowed = false;
622623
}
623624

625+
/*
626+
* Update ShmemVariableCache->latestCompletedXid to point to latestXid if
627+
* currently older.
628+
*/
629+
static void
630+
MaintainLatestCompletedXid(TransactionId latestXid)
631+
{
632+
FullTransactionId cur_latest = ShmemVariableCache->latestCompletedXid;
633+
634+
Assert(FullTransactionIdIsValid(cur_latest));
635+
Assert(!RecoveryInProgress());
636+
Assert(LWLockHeldByMe(ProcArrayLock));
637+
638+
if (TransactionIdPrecedes(XidFromFullTransactionId(cur_latest), latestXid))
639+
{
640+
ShmemVariableCache->latestCompletedXid =
641+
FullXidRelativeTo(cur_latest, latestXid);
642+
}
643+
644+
Assert(IsBootstrapProcessingMode() ||
645+
FullTransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
646+
}
647+
648+
/*
649+
* Same as MaintainLatestCompletedXid, except for use during WAL replay.
650+
*/
651+
static void
652+
MaintainLatestCompletedXidRecovery(TransactionId latestXid)
653+
{
654+
FullTransactionId cur_latest = ShmemVariableCache->latestCompletedXid;
655+
FullTransactionId rel;
656+
657+
Assert(AmStartupProcess() || !IsUnderPostmaster);
658+
Assert(LWLockHeldByMe(ProcArrayLock));
659+
660+
/*
661+
* Need a FullTransactionId to compare latestXid with. Can't rely on
662+
* latestCompletedXid to be initialized in recovery. But in recovery it's
663+
* safe to access nextXid without a lock for the startup process.
664+
*/
665+
rel = ShmemVariableCache->nextXid;
666+
Assert(FullTransactionIdIsValid(ShmemVariableCache->nextXid));
667+
668+
if (!FullTransactionIdIsValid(cur_latest) ||
669+
TransactionIdPrecedes(XidFromFullTransactionId(cur_latest), latestXid))
670+
{
671+
ShmemVariableCache->latestCompletedXid =
672+
FullXidRelativeTo(rel, latestXid);
673+
}
674+
675+
Assert(FullTransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
676+
}
677+
624678
/*
625679
* ProcArrayInitRecovery -- initialize recovery xid mgmt environment
626680
*
@@ -869,12 +923,9 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
869923
* If a transaction wrote a commit record in the gap between taking and
870924
* logging the snapshot then latestCompletedXid may already be higher than
871925
* the value from the snapshot, so check before we use the incoming value.
926+
* It also might not yet be set at all.
872927
*/
873-
if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
874-
running->latestCompletedXid))
875-
ShmemVariableCache->latestCompletedXid = running->latestCompletedXid;
876-
877-
Assert(TransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
928+
MaintainLatestCompletedXidRecovery(running->latestCompletedXid);
878929

879930
LWLockRelease(ProcArrayLock);
880931

@@ -989,6 +1040,7 @@ TransactionIdIsInProgress(TransactionId xid)
9891040
int nxids = 0;
9901041
ProcArrayStruct *arrayP = procArray;
9911042
TransactionId topxid;
1043+
TransactionId latestCompletedXid;
9921044
int i,
9931045
j;
9941046

@@ -1051,7 +1103,9 @@ TransactionIdIsInProgress(TransactionId xid)
10511103
* Now that we have the lock, we can check latestCompletedXid; if the
10521104
* target Xid is after that, it's surely still running.
10531105
*/
1054-
if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid, xid))
1106+
latestCompletedXid =
1107+
XidFromFullTransactionId(ShmemVariableCache->latestCompletedXid);
1108+
if (TransactionIdPrecedes(latestCompletedXid, xid))
10551109
{
10561110
LWLockRelease(ProcArrayLock);
10571111
xc_by_latest_xid_inc();
@@ -1330,9 +1384,9 @@ GetOldestXmin(Relation rel, int flags)
13301384
* and so protects us against overestimating the result due to future
13311385
* additions.
13321386
*/
1333-
result = ShmemVariableCache->latestCompletedXid;
1334-
Assert(TransactionIdIsNormal(result));
1387+
result = XidFromFullTransactionId(ShmemVariableCache->latestCompletedXid);
13351388
TransactionIdAdvance(result);
1389+
Assert(TransactionIdIsNormal(result));
13361390

13371391
for (index = 0; index < arrayP->numProcs; index++)
13381392
{
@@ -1511,6 +1565,7 @@ GetSnapshotData(Snapshot snapshot)
15111565
int count = 0;
15121566
int subcount = 0;
15131567
bool suboverflowed = false;
1568+
FullTransactionId latest_completed;
15141569
TransactionId replication_slot_xmin = InvalidTransactionId;
15151570
TransactionId replication_slot_catalog_xmin = InvalidTransactionId;
15161571

@@ -1554,10 +1609,11 @@ GetSnapshotData(Snapshot snapshot)
15541609
*/
15551610
LWLockAcquire(ProcArrayLock, LW_SHARED);
15561611

1612+
latest_completed = ShmemVariableCache->latestCompletedXid;
15571613
/* xmax is always latestCompletedXid + 1 */
1558-
xmax = ShmemVariableCache->latestCompletedXid;
1559-
Assert(TransactionIdIsNormal(xmax));
1614+
xmax = XidFromFullTransactionId(latest_completed);
15601615
TransactionIdAdvance(xmax);
1616+
Assert(TransactionIdIsNormal(xmax));
15611617

15621618
/* initialize xmin calculation with xmax */
15631619
globalxmin = xmin = xmax;
@@ -1984,9 +2040,10 @@ GetRunningTransactionData(void)
19842040
LWLockAcquire(ProcArrayLock, LW_SHARED);
19852041
LWLockAcquire(XidGenLock, LW_SHARED);
19862042

1987-
latestCompletedXid = ShmemVariableCache->latestCompletedXid;
1988-
1989-
oldestRunningXid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
2043+
latestCompletedXid =
2044+
XidFromFullTransactionId(ShmemVariableCache->latestCompletedXid);
2045+
oldestRunningXid =
2046+
XidFromFullTransactionId(ShmemVariableCache->nextXid);
19902047

19912048
/*
19922049
* Spin over procArray collecting all xids
@@ -3207,9 +3264,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
32073264
elog(WARNING, "did not find subXID %u in MyProc", xid);
32083265

32093266
/* Also advance global latestCompletedXid while holding the lock */
3210-
if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
3211-
latestXid))
3212-
ShmemVariableCache->latestCompletedXid = latestXid;
3267+
MaintainLatestCompletedXid(latestXid);
32133268

32143269
LWLockRelease(ProcArrayLock);
32153270
}
@@ -3236,6 +3291,32 @@ DisplayXidCache(void)
32363291
}
32373292
#endif /* XIDCACHE_DEBUG */
32383293

3294+
/*
3295+
* Convert a 32 bit transaction id into 64 bit transaction id, by assuming it
3296+
* is within MaxTransactionId / 2 of XidFromFullTransactionId(rel).
3297+
*
3298+
* Be very careful about when to use this function. It can only safely be used
3299+
* when there is a guarantee that xid is within MaxTransactionId / 2 xids of
3300+
* rel. That e.g. can be guaranteed if the the caller assures a snapshot is
3301+
* held by the backend and xid is from a table (where vacuum/freezing ensures
3302+
* the xid has to be within that range), or if xid is from the procarray and
3303+
* prevents xid wraparound that way.
3304+
*/
3305+
static inline FullTransactionId
3306+
FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
3307+
{
3308+
TransactionId rel_xid = XidFromFullTransactionId(rel);
3309+
3310+
Assert(TransactionIdIsValid(xid));
3311+
Assert(TransactionIdIsValid(rel_xid));
3312+
3313+
/* not guaranteed to find issues, but likely to catch mistakes */
3314+
AssertTransactionIdInAllowableRange(xid);
3315+
3316+
return FullTransactionIdFromU64(U64FromFullTransactionId(rel)
3317+
+ (int32) (xid - rel_xid));
3318+
}
3319+
32393320

32403321
/* ----------------------------------------------
32413322
* KnownAssignedTransactionIds sub-module
@@ -3388,9 +3469,7 @@ ExpireTreeKnownAssignedTransactionIds(TransactionId xid, int nsubxids,
33883469
KnownAssignedXidsRemoveTree(xid, nsubxids, subxids);
33893470

33903471
/* As in ProcArrayEndTransaction, advance latestCompletedXid */
3391-
if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
3392-
max_xid))
3393-
ShmemVariableCache->latestCompletedXid = max_xid;
3472+
MaintainLatestCompletedXidRecovery(max_xid);
33943473

33953474
LWLockRelease(ProcArrayLock);
33963475
}

src/include/access/transam.h

+35-2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
#define FullTransactionIdFollowsOrEquals(a, b) ((a).value >= (b).value)
5555
#define FullTransactionIdIsValid(x) TransactionIdIsValid(XidFromFullTransactionId(x))
5656
#define InvalidFullTransactionId FullTransactionIdFromEpochAndXid(0, InvalidTransactionId)
57+
#define FirstNormalFullTransactionId FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId)
58+
#define FullTransactionIdIsNormal(x) FullTransactionIdFollowsOrEquals(x, FirstNormalFullTransactionId)
5759

5860
/*
5961
* A 64 bit value that contains an epoch and a TransactionId. This is
@@ -102,6 +104,31 @@ FullTransactionIdAdvance(FullTransactionId *dest)
102104
dest->value++;
103105
}
104106

107+
/*
108+
* Retreat a FullTransactionId variable, stepping over xids that would appear
109+
* to be special only when viewed as 32bit XIDs.
110+
*/
111+
static inline void
112+
FullTransactionIdRetreat(FullTransactionId *dest)
113+
{
114+
dest->value--;
115+
116+
/*
117+
* In contrast to 32bit XIDs don't step over the "actual" special xids.
118+
* For 64bit xids these can't be reached as part of a wraparound as they
119+
* can in the 32bit case.
120+
*/
121+
if (FullTransactionIdPrecedes(*dest, FirstNormalFullTransactionId))
122+
return;
123+
124+
/*
125+
* But we do need to step over XIDs that'd appear special only for 32bit
126+
* XIDs.
127+
*/
128+
while (XidFromFullTransactionId(*dest) < FirstNormalTransactionId)
129+
dest->value--;
130+
}
131+
105132
/* back up a transaction ID variable, handling wraparound correctly */
106133
#define TransactionIdRetreat(dest) \
107134
do { \
@@ -193,8 +220,8 @@ typedef struct VariableCacheData
193220
/*
194221
* These fields are protected by ProcArrayLock.
195222
*/
196-
TransactionId latestCompletedXid; /* newest XID that has committed or
197-
* aborted */
223+
FullTransactionId latestCompletedXid; /* newest full XID that has
224+
* committed or aborted */
198225

199226
/*
200227
* These fields are protected by XactTruncationLock
@@ -244,6 +271,12 @@ extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid);
244271
extern bool ForceTransactionIdLimitUpdate(void);
245272
extern Oid GetNewObjectId(void);
246273

274+
#ifdef USE_ASSERT_CHECKING
275+
extern void AssertTransactionIdInAllowableRange(TransactionId xid);
276+
#else
277+
#define AssertTransactionIdInAllowableRange(xid) ((void)true)
278+
#endif
279+
247280
/*
248281
* Some frontend programs include this header. For compilers that emit static
249282
* inline functions even when they're unused, that leads to unsatisfied

0 commit comments

Comments
 (0)