/* * Get new XID. For global transaction is it previsly set by dtm_begin_transaction or dtm_join_transaction. * Local transactions are using range of local Xids obtains from DTM. */ static TransactionId DtmGetNextXid() { TransactionId xid; LWLockAcquire(dtm->xidLock, LW_EXCLUSIVE); if (TransactionIdIsValid(DtmNextXid)) { XTM_INFO("Use global XID %d\n", DtmNextXid); xid = DtmNextXid; if (TransactionIdPrecedesOrEquals(ShmemVariableCache->nextXid, xid)) { /* Advance ShmemVariableCache->nextXid formward until new Xid */ while (TransactionIdPrecedes(ShmemVariableCache->nextXid, xid)) { XTM_INFO("Extend CLOG for global transaction to %d\n", ShmemVariableCache->nextXid); ExtendCLOG(ShmemVariableCache->nextXid); ExtendCommitTs(ShmemVariableCache->nextXid); ExtendSUBTRANS(ShmemVariableCache->nextXid); TransactionIdAdvance(ShmemVariableCache->nextXid); } dtm->nReservedXids = 0; } } else { if (dtm->nReservedXids == 0) { XTM_INFO("%d: reserve new XID range\n", getpid()); dtm->nReservedXids = ArbiterReserve(ShmemVariableCache->nextXid, DtmLocalXidReserve, &dtm->nextXid); Assert(dtm->nReservedXids > 0); Assert(TransactionIdFollowsOrEquals(dtm->nextXid, ShmemVariableCache->nextXid)); /* Advance ShmemVariableCache->nextXid formward until new Xid */ while (TransactionIdPrecedes(ShmemVariableCache->nextXid, dtm->nextXid)) { XTM_INFO("Extend CLOG for local transaction to %d\n", ShmemVariableCache->nextXid); ExtendCLOG(ShmemVariableCache->nextXid); ExtendCommitTs(ShmemVariableCache->nextXid); ExtendSUBTRANS(ShmemVariableCache->nextXid); TransactionIdAdvance(ShmemVariableCache->nextXid); } } Assert(ShmemVariableCache->nextXid == dtm->nextXid); xid = dtm->nextXid++; dtm->nReservedXids -= 1; XTM_INFO("Obtain new local XID %d\n", xid); } LWLockRelease(dtm->xidLock); return xid; }
Datum spoof_next_xid(PG_FUNCTION_ARGS) { TransactionId desiredXid = PG_GETARG_UINT32(0); TransactionId oldXid = ShmemVariableCache->nextXid; ShmemVariableCache->nextXid = desiredXid; /* * If we're raising the xid, the intent is presumably to cross some * threshold and make assertions about expected behavior. * On the other hand, lowering the xid is meant to be a tear down of * a completed test case. Because of this distinction, only when * we're raising the xid, do we take extra precaution to zero out * the new pg_clog/pg_subtrans/pg_distributedlog files. (We don't * want to zero out existing files...) */ if (TransactionIdFollows(desiredXid, oldXid)) { /* * The nature of xid arithmetic is such that we only bother zeroing out * new pages of transaction files when we've crossed page boundaries. * So, here we fool the following routines into zeroing out the desired * pages of transaction metadata by lowering the input xid to the first * of its corresponding page. */ #define CLOG_XACTS_PER_BYTE 4 #define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE) #define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE) ExtendCLOG(desiredXid - TransactionIdToPgIndex(desiredXid)); #define SUBTRANS_XACTS_PER_PAGE (BLCKSZ / sizeof(SubTransData)) #define TransactionIdToEntry(xid) ((xid) % (uint32) SUBTRANS_XACTS_PER_PAGE) ExtendSUBTRANS(desiredXid - TransactionIdToEntry(desiredXid)); #undef TransactionIdToEntry #define ENTRIES_PER_PAGE (BLCKSZ / sizeof(DistributedLogEntry)) #define TransactionIdToEntry(localXid) ((localXid) % (TransactionId) ENTRIES_PER_PAGE) DistributedLog_Extend(desiredXid - TransactionIdToEntry(desiredXid)); } PG_RETURN_XID(oldXid); }
/* * We have to cut&paste copde of GetNewTransactionId from varsup because we change way of advancing ShmemVariableCache->nextXid */ TransactionId DtmGetNewTransactionId(bool isSubXact) { TransactionId xid; XTM_INFO("%d: GetNewTransactionId\n", getpid()); /* * Workers synchronize transaction state at the beginning of each parallel * operation, so we can't account for new XIDs after that point. */ if (IsInParallelMode()) elog(ERROR, "cannot assign TransactionIds during a parallel operation"); /* * During bootstrap initialization, we return the special bootstrap * transaction id. */ if (IsBootstrapProcessingMode()) { Assert(!isSubXact); MyPgXact->xid = BootstrapTransactionId; return BootstrapTransactionId; } /* safety check, we should never get this far in a HS slave */ if (RecoveryInProgress()) elog(ERROR, "cannot assign TransactionIds during recovery"); LWLockAcquire(XidGenLock, LW_EXCLUSIVE); xid = DtmGetNextXid(); /*---------- * Check to see if it's safe to assign another XID. This protects against * catastrophic data loss due to XID wraparound. The basic rules are: * * If we're past xidVacLimit, start trying to force autovacuum cycles. * If we're past xidWarnLimit, start issuing warnings. * If we're past xidStopLimit, refuse to execute transactions, unless * we are running in single-user mode (which gives an escape hatch * to the DBA who somehow got past the earlier defenses). * * Note that this coding also appears in GetNewMultiXactId. *---------- */ if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidVacLimit)) { /* * For safety's sake, we release XidGenLock while sending signals, * warnings, etc. This is not so much because we care about * preserving concurrency in this situation, as to avoid any * possibility of deadlock while doing get_database_name(). First, * copy all the shared values we'll need in this path. */ TransactionId xidWarnLimit = ShmemVariableCache->xidWarnLimit; TransactionId xidStopLimit = ShmemVariableCache->xidStopLimit; TransactionId xidWrapLimit = ShmemVariableCache->xidWrapLimit; Oid oldest_datoid = ShmemVariableCache->oldestXidDB; LWLockRelease(XidGenLock); /* * To avoid swamping the postmaster with signals, we issue the autovac * request only once per 64K transaction starts. This still gives * plenty of chances before we get into real trouble. */ if (IsUnderPostmaster && (xid % 65536) == 0) SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER); if (IsUnderPostmaster && TransactionIdFollowsOrEquals(xid, xidStopLimit)) { char *oldest_datname = get_database_name(oldest_datoid); /* complain even if that DB has disappeared */ if (oldest_datname) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("database is not accepting commands to avoid wraparound data loss in database \"%s\"", oldest_datname), errhint("Stop the postmaster and vacuum that database in single-user mode.\n" "You might also need to commit or roll back old prepared transactions."))); else ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("database is not accepting commands to avoid wraparound data loss in database with OID %u", oldest_datoid), errhint("Stop the postmaster and vacuum that database in single-user mode.\n" "You might also need to commit or roll back old prepared transactions."))); } else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit)) { char *oldest_datname = get_database_name(oldest_datoid); /* complain even if that DB has disappeared */ if (oldest_datname) ereport(WARNING, (errmsg("database \"%s\" must be vacuumed within %u transactions", oldest_datname, xidWrapLimit - xid), errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n" "You might also need to commit or roll back old prepared transactions."))); else ereport(WARNING, (errmsg("database with OID %u must be vacuumed within %u transactions", oldest_datoid, xidWrapLimit - xid), errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n" "You might also need to commit or roll back old prepared transactions."))); } /* Re-acquire lock and start over */ LWLockAcquire(XidGenLock, LW_EXCLUSIVE); xid = DtmGetNextXid(); } /* * If we are allocating the first XID of a new page of the commit log, * zero out that commit-log page before returning. We must do this while * holding XidGenLock, else another xact could acquire and commit a later * XID before we zero the page. Fortunately, a page of the commit log * holds 32K or more transactions, so we don't have to do this very often. * * Extend pg_subtrans and pg_commit_ts too. */ if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->nextXid)) { ExtendCLOG(xid); ExtendCommitTs(xid); ExtendSUBTRANS(xid); } /* * Now advance the nextXid counter. This must not happen until after we * have successfully completed ExtendCLOG() --- if that routine fails, we * want the next incoming transaction to try it again. We cannot assign * more XIDs until there is CLOG space for them. */ if (xid == ShmemVariableCache->nextXid) TransactionIdAdvance(ShmemVariableCache->nextXid); else Assert(TransactionIdPrecedes(xid, ShmemVariableCache->nextXid)); /* * We must store the new XID into the shared ProcArray before releasing * XidGenLock. This ensures that every active XID older than * latestCompletedXid is present in the ProcArray, which is essential for * correct OldestXmin tracking; see src/backend/access/transam/README. * * XXX by storing xid into MyPgXact without acquiring ProcArrayLock, we * are relying on fetch/store of an xid to be atomic, else other backends * might see a partially-set xid here. But holding both locks at once * would be a nasty concurrency hit. So for now, assume atomicity. * * Note that readers of PGXACT xid fields should be careful to fetch the * value only once, rather than assume they can read a value multiple * times and get the same answer each time. * * The same comments apply to the subxact xid count and overflow fields. * * A solution to the atomic-store problem would be to give each PGXACT its * own spinlock used only for fetching/storing that PGXACT's xid and * related fields. * * If there's no room to fit a subtransaction XID into PGPROC, set the * cache-overflowed flag instead. This forces readers to look in * pg_subtrans to map subtransaction XIDs up to top-level XIDs. There is a * race-condition window, in that the new XID will not appear as running * until its parent link has been placed into pg_subtrans. However, that * will happen before anyone could possibly have a reason to inquire about * the status of the XID, so it seems OK. (Snapshots taken during this * window *will* include the parent XID, so they will deliver the correct * answer later on when someone does have a reason to inquire.) */ { /* * Use volatile pointer to prevent code rearrangement; other backends * could be examining my subxids info concurrently, and we don't want * them to see an invalid intermediate state, such as incrementing * nxids before filling the array entry. Note we are assuming that * TransactionId and int fetch/store are atomic. */ volatile PGPROC *myproc = MyProc; volatile PGXACT *mypgxact = MyPgXact; if (!isSubXact) mypgxact->xid = xid; else { int nxids = mypgxact->nxids; if (nxids < PGPROC_MAX_CACHED_SUBXIDS) { myproc->subxids.xids[nxids] = xid; mypgxact->nxids = nxids + 1; } else mypgxact->overflowed = true; } } LWLockRelease(XidGenLock); return xid; }