aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/transam/varsup.c50
-rw-r--r--src/backend/access/transam/xlog.c4
-rw-r--r--src/backend/storage/ipc/procarray.c129
-rw-r--r--src/include/access/transam.h37
4 files changed, 191 insertions, 29 deletions
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 3ebd75118f0..2ef0f4991ca 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -569,3 +569,53 @@ GetNewObjectId(void)
return result;
}
+
+
+#ifdef USE_ASSERT_CHECKING
+
+/*
+ * Assert that xid is between [oldestXid, nextXid], which is the range we
+ * expect XIDs coming from tables etc to be in.
+ *
+ * As ShmemVariableCache->oldestXid could change just after this call without
+ * further precautions, and as a wrapped-around xid could again fall within
+ * the valid range, this assertion can only detect if something is definitely
+ * wrong, but not establish correctness.
+ *
+ * This intentionally does not expose a return value, to avoid code being
+ * introduced that depends on the return value.
+ */
+void
+AssertTransactionIdInAllowableRange(TransactionId xid)
+{
+ TransactionId oldest_xid;
+ TransactionId next_xid;
+
+ Assert(TransactionIdIsValid(xid));
+
+ /* we may see bootstrap / frozen */
+ if (!TransactionIdIsNormal(xid))
+ return;
+
+ /*
+ * We can't acquire XidGenLock, as this may be called with XidGenLock
+ * already held (or with other locks that don't allow XidGenLock to be
+ * nested). That's ok for our purposes though, since we already rely on
+ * 32bit reads to be atomic. While nextXid is 64 bit, we only look at
+ * the lower 32bit, so a skewed read doesn't hurt.
+ *
+ * There's no increased danger of falling outside [oldest, next] by
+ * accessing them without a lock. xid needs to have been created with
+ * GetNewTransactionId() in the originating session, and the locks there
+ * pair with the memory barrier below. We do however accept xid to be <=
+ * to next_xid, instead of just <, as xid could be from the procarray,
+ * before we see the updated nextXid value.
+ */
+ pg_memory_barrier();
+ oldest_xid = ShmemVariableCache->oldestXid;
+ next_xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
+
+ Assert(TransactionIdFollowsOrEquals(xid, oldest_xid) ||
+ TransactionIdPrecedesOrEquals(xid, next_xid));
+}
+#endif
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 53945c0e305..8f72faee82c 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -7865,8 +7865,8 @@ StartupXLOG(void)
/* also initialize latestCompletedXid, to nextXid - 1 */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- ShmemVariableCache->latestCompletedXid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
- TransactionIdRetreat(ShmemVariableCache->latestCompletedXid);
+ ShmemVariableCache->latestCompletedXid = ShmemVariableCache->nextXid;
+ FullTransactionIdRetreat(&ShmemVariableCache->latestCompletedXid);
LWLockRelease(ProcArrayLock);
/*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index be0240e0ddc..522518695ee 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -175,6 +175,11 @@ static void KnownAssignedXidsReset(void);
static inline void ProcArrayEndTransactionInternal(PGPROC *proc,
PGXACT *pgxact, TransactionId latestXid);
static void ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid);
+static void MaintainLatestCompletedXid(TransactionId latestXid);
+static void MaintainLatestCompletedXidRecovery(TransactionId latestXid);
+
+static inline FullTransactionId FullXidRelativeTo(FullTransactionId rel,
+ TransactionId xid);
/*
* Report shared-memory space needed by CreateSharedProcArray.
@@ -349,9 +354,7 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
Assert(TransactionIdIsValid(allPgXact[proc->pgprocno].xid));
/* Advance global latestCompletedXid while holding the lock */
- if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
- latestXid))
- ShmemVariableCache->latestCompletedXid = latestXid;
+ MaintainLatestCompletedXid(latestXid);
}
else
{
@@ -464,9 +467,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact,
pgxact->overflowed = false;
/* Also advance global latestCompletedXid while holding the lock */
- if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
- latestXid))
- ShmemVariableCache->latestCompletedXid = latestXid;
+ MaintainLatestCompletedXid(latestXid);
}
/*
@@ -622,6 +623,59 @@ ProcArrayClearTransaction(PGPROC *proc)
}
/*
+ * Update ShmemVariableCache->latestCompletedXid to point to latestXid if
+ * currently older.
+ */
+static void
+MaintainLatestCompletedXid(TransactionId latestXid)
+{
+ FullTransactionId cur_latest = ShmemVariableCache->latestCompletedXid;
+
+ Assert(FullTransactionIdIsValid(cur_latest));
+ Assert(!RecoveryInProgress());
+ Assert(LWLockHeldByMe(ProcArrayLock));
+
+ if (TransactionIdPrecedes(XidFromFullTransactionId(cur_latest), latestXid))
+ {
+ ShmemVariableCache->latestCompletedXid =
+ FullXidRelativeTo(cur_latest, latestXid);
+ }
+
+ Assert(IsBootstrapProcessingMode() ||
+ FullTransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
+}
+
+/*
+ * Same as MaintainLatestCompletedXid, except for use during WAL replay.
+ */
+static void
+MaintainLatestCompletedXidRecovery(TransactionId latestXid)
+{
+ FullTransactionId cur_latest = ShmemVariableCache->latestCompletedXid;
+ FullTransactionId rel;
+
+ Assert(AmStartupProcess() || !IsUnderPostmaster);
+ Assert(LWLockHeldByMe(ProcArrayLock));
+
+ /*
+ * Need a FullTransactionId to compare latestXid with. Can't rely on
+ * latestCompletedXid to be initialized in recovery. But in recovery it's
+ * safe to access nextXid without a lock for the startup process.
+ */
+ rel = ShmemVariableCache->nextXid;
+ Assert(FullTransactionIdIsValid(ShmemVariableCache->nextXid));
+
+ if (!FullTransactionIdIsValid(cur_latest) ||
+ TransactionIdPrecedes(XidFromFullTransactionId(cur_latest), latestXid))
+ {
+ ShmemVariableCache->latestCompletedXid =
+ FullXidRelativeTo(rel, latestXid);
+ }
+
+ Assert(FullTransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
+}
+
+/*
* ProcArrayInitRecovery -- initialize recovery xid mgmt environment
*
* Remember up to where the startup process initialized the CLOG and subtrans
@@ -869,12 +923,9 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
* If a transaction wrote a commit record in the gap between taking and
* logging the snapshot then latestCompletedXid may already be higher than
* the value from the snapshot, so check before we use the incoming value.
+ * It also might not yet be set at all.
*/
- if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
- running->latestCompletedXid))
- ShmemVariableCache->latestCompletedXid = running->latestCompletedXid;
-
- Assert(TransactionIdIsNormal(ShmemVariableCache->latestCompletedXid));
+ MaintainLatestCompletedXidRecovery(running->latestCompletedXid);
LWLockRelease(ProcArrayLock);
@@ -989,6 +1040,7 @@ TransactionIdIsInProgress(TransactionId xid)
int nxids = 0;
ProcArrayStruct *arrayP = procArray;
TransactionId topxid;
+ TransactionId latestCompletedXid;
int i,
j;
@@ -1051,7 +1103,9 @@ TransactionIdIsInProgress(TransactionId xid)
* Now that we have the lock, we can check latestCompletedXid; if the
* target Xid is after that, it's surely still running.
*/
- if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid, xid))
+ latestCompletedXid =
+ XidFromFullTransactionId(ShmemVariableCache->latestCompletedXid);
+ if (TransactionIdPrecedes(latestCompletedXid, xid))
{
LWLockRelease(ProcArrayLock);
xc_by_latest_xid_inc();
@@ -1330,9 +1384,9 @@ GetOldestXmin(Relation rel, int flags)
* and so protects us against overestimating the result due to future
* additions.
*/
- result = ShmemVariableCache->latestCompletedXid;
- Assert(TransactionIdIsNormal(result));
+ result = XidFromFullTransactionId(ShmemVariableCache->latestCompletedXid);
TransactionIdAdvance(result);
+ Assert(TransactionIdIsNormal(result));
for (index = 0; index < arrayP->numProcs; index++)
{
@@ -1511,6 +1565,7 @@ GetSnapshotData(Snapshot snapshot)
int count = 0;
int subcount = 0;
bool suboverflowed = false;
+ FullTransactionId latest_completed;
TransactionId replication_slot_xmin = InvalidTransactionId;
TransactionId replication_slot_catalog_xmin = InvalidTransactionId;
@@ -1554,10 +1609,11 @@ GetSnapshotData(Snapshot snapshot)
*/
LWLockAcquire(ProcArrayLock, LW_SHARED);
+ latest_completed = ShmemVariableCache->latestCompletedXid;
/* xmax is always latestCompletedXid + 1 */
- xmax = ShmemVariableCache->latestCompletedXid;
- Assert(TransactionIdIsNormal(xmax));
+ xmax = XidFromFullTransactionId(latest_completed);
TransactionIdAdvance(xmax);
+ Assert(TransactionIdIsNormal(xmax));
/* initialize xmin calculation with xmax */
globalxmin = xmin = xmax;
@@ -1984,9 +2040,10 @@ GetRunningTransactionData(void)
LWLockAcquire(ProcArrayLock, LW_SHARED);
LWLockAcquire(XidGenLock, LW_SHARED);
- latestCompletedXid = ShmemVariableCache->latestCompletedXid;
-
- oldestRunningXid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
+ latestCompletedXid =
+ XidFromFullTransactionId(ShmemVariableCache->latestCompletedXid);
+ oldestRunningXid =
+ XidFromFullTransactionId(ShmemVariableCache->nextXid);
/*
* Spin over procArray collecting all xids
@@ -3207,9 +3264,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
elog(WARNING, "did not find subXID %u in MyProc", xid);
/* Also advance global latestCompletedXid while holding the lock */
- if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
- latestXid))
- ShmemVariableCache->latestCompletedXid = latestXid;
+ MaintainLatestCompletedXid(latestXid);
LWLockRelease(ProcArrayLock);
}
@@ -3236,6 +3291,32 @@ DisplayXidCache(void)
}
#endif /* XIDCACHE_DEBUG */
+/*
+ * Convert a 32 bit transaction id into 64 bit transaction id, by assuming it
+ * is within MaxTransactionId / 2 of XidFromFullTransactionId(rel).
+ *
+ * Be very careful about when to use this function. It can only safely be used
+ * when there is a guarantee that xid is within MaxTransactionId / 2 xids of
+ * rel. That e.g. can be guaranteed if the the caller assures a snapshot is
+ * held by the backend and xid is from a table (where vacuum/freezing ensures
+ * the xid has to be within that range), or if xid is from the procarray and
+ * prevents xid wraparound that way.
+ */
+static inline FullTransactionId
+FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
+{
+ TransactionId rel_xid = XidFromFullTransactionId(rel);
+
+ Assert(TransactionIdIsValid(xid));
+ Assert(TransactionIdIsValid(rel_xid));
+
+ /* not guaranteed to find issues, but likely to catch mistakes */
+ AssertTransactionIdInAllowableRange(xid);
+
+ return FullTransactionIdFromU64(U64FromFullTransactionId(rel)
+ + (int32) (xid - rel_xid));
+}
+
/* ----------------------------------------------
* KnownAssignedTransactionIds sub-module
@@ -3388,9 +3469,7 @@ ExpireTreeKnownAssignedTransactionIds(TransactionId xid, int nsubxids,
KnownAssignedXidsRemoveTree(xid, nsubxids, subxids);
/* As in ProcArrayEndTransaction, advance latestCompletedXid */
- if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
- max_xid))
- ShmemVariableCache->latestCompletedXid = max_xid;
+ MaintainLatestCompletedXidRecovery(max_xid);
LWLockRelease(ProcArrayLock);
}
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 85508300e9a..8db326ad1b5 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -54,6 +54,8 @@
#define FullTransactionIdFollowsOrEquals(a, b) ((a).value >= (b).value)
#define FullTransactionIdIsValid(x) TransactionIdIsValid(XidFromFullTransactionId(x))
#define InvalidFullTransactionId FullTransactionIdFromEpochAndXid(0, InvalidTransactionId)
+#define FirstNormalFullTransactionId FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId)
+#define FullTransactionIdIsNormal(x) FullTransactionIdFollowsOrEquals(x, FirstNormalFullTransactionId)
/*
* A 64 bit value that contains an epoch and a TransactionId. This is
@@ -102,6 +104,31 @@ FullTransactionIdAdvance(FullTransactionId *dest)
dest->value++;
}
+/*
+ * Retreat a FullTransactionId variable, stepping over xids that would appear
+ * to be special only when viewed as 32bit XIDs.
+ */
+static inline void
+FullTransactionIdRetreat(FullTransactionId *dest)
+{
+ dest->value--;
+
+ /*
+ * In contrast to 32bit XIDs don't step over the "actual" special xids.
+ * For 64bit xids these can't be reached as part of a wraparound as they
+ * can in the 32bit case.
+ */
+ if (FullTransactionIdPrecedes(*dest, FirstNormalFullTransactionId))
+ return;
+
+ /*
+ * But we do need to step over XIDs that'd appear special only for 32bit
+ * XIDs.
+ */
+ while (XidFromFullTransactionId(*dest) < FirstNormalTransactionId)
+ dest->value--;
+}
+
/* back up a transaction ID variable, handling wraparound correctly */
#define TransactionIdRetreat(dest) \
do { \
@@ -193,8 +220,8 @@ typedef struct VariableCacheData
/*
* These fields are protected by ProcArrayLock.
*/
- TransactionId latestCompletedXid; /* newest XID that has committed or
- * aborted */
+ FullTransactionId latestCompletedXid; /* newest full XID that has
+ * committed or aborted */
/*
* These fields are protected by XactTruncationLock
@@ -244,6 +271,12 @@ extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid);
extern bool ForceTransactionIdLimitUpdate(void);
extern Oid GetNewObjectId(void);
+#ifdef USE_ASSERT_CHECKING
+extern void AssertTransactionIdInAllowableRange(TransactionId xid);
+#else
+#define AssertTransactionIdInAllowableRange(xid) ((void)true)
+#endif
+
/*
* Some frontend programs include this header. For compilers that emit static
* inline functions even when they're unused, that leads to unsatisfied