diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 6ea0a28..cd0b39c 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -51,6 +51,7 @@ #include "access/xact.h" #include "access/twophase.h" #include "miscadmin.h" +#include "storage/barrier.h" #include "storage/procarray.h" #include "storage/spin.h" #include "utils/builtins.h" @@ -81,6 +82,9 @@ typedef struct ProcArrayStruct */ TransactionId lastOverflowedXid; + /* Increment on transaction commit/abort. */ + TransactionId xendcount; + /* * We declare pgprocnos[] as 1 entry because C wants a fixed-size array, but * actually it is maxProcs entries long. @@ -223,7 +227,13 @@ CreateSharedProcArray(void) { /* * We're the first - initialize. + * + * Note: We need to initialize procArray->xfinish to something other + * than 0, because we're going to later compare it against the + * xendcount of a snapshot to see if anything's changed; and 0 in the + * snapshot means it's as-yet uninitialized. */ + procArray->xendcount = 1; procArray->numProcs = 0; procArray->maxProcs = PROCARRAY_MAXPROCS; procArray->maxKnownAssignedXids = TOTAL_MAX_CACHED_SUBXIDS; @@ -410,6 +420,8 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) latestXid)) ShmemVariableCache->latestCompletedXid = latestXid; + procArray->xendcount++; + LWLockRelease(ProcArrayLock); } else @@ -1245,6 +1257,27 @@ GetSnapshotData(Snapshot snapshot) Assert(snapshot != NULL); /* + * If no transactions have committed or aborted since the last time this + * function was called on the passed-in snapshot, we can return without + * doing anything. + * + * Memory ordering effects: It's possible that the procArray->xendcount + * could be fetched by the CPU prior to entering this function, in which + * case we might see an "old" value that matches instead of a "new" value + * that doesn't. But that's not much different than if this function had + * been called slightly sooner in the first place. Just to be on the safe + * side, include a read barrier, so that this fetch won't be done before + * all prior fetches have been completed. + * + * XXX: This is theoretically unsafe if 64-bit reads and writes from shared + * memory aren't atomic, but in practice you'd have to be incredibly + * unlucky to have a problem. + */ + pg_read_barrier(); + if (procArray->xendcount == snapshot->xendcount) + return snapshot; + + /* * Allocating space for maxProcs xids is usually overkill; numProcs would * be sufficient. But it seems better to do the malloc while not holding * the lock, so we can't look at numProcs. Likewise, we allocate much @@ -1283,6 +1316,7 @@ GetSnapshotData(Snapshot snapshot) LWLockAcquire(ProcArrayLock, LW_SHARED); /* xmax is always latestCompletedXid + 1 */ + snapshot->xendcount = arrayP->xendcount; xmax = ShmemVariableCache->latestCompletedXid; Assert(TransactionIdIsNormal(xmax)); TransactionIdAdvance(xmax); diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h index 93c02fa..d6a3a68 100644 --- a/src/include/utils/snapshot.h +++ b/src/include/utils/snapshot.h @@ -44,12 +44,13 @@ typedef struct SnapshotData * is stored as an optimization to avoid needing to search the XID arrays * for most tuples. */ + uint64 xendcount; /* when did we take this snapshot? */ TransactionId xmin; /* all XID < xmin are visible to me */ TransactionId xmax; /* all XID >= xmax are invisible to me */ uint32 xcnt; /* # of xact ids in xip[] */ - TransactionId *xip; /* array of xact IDs in progress */ /* note: all ids in xip[] satisfy xmin <= xip[i] < xmax */ int32 subxcnt; /* # of xact ids in subxip[] */ + TransactionId *xip; /* array of xact IDs in progress */ TransactionId *subxip; /* array of subxact IDs in progress */ bool suboverflowed; /* has the subxip array overflowed? */ bool takenDuringRecovery; /* recovery-shaped snapshot? */