/* * Record the parent of a subtransaction in the subtrans log. * * In some cases we may need to overwrite an existing value. */ void SubTransSetParent(TransactionId xid, TransactionId parent, bool overwriteOK) { int pageno = TransactionIdToPage(xid); int entryno = TransactionIdToEntry(xid); int slotno; TransactionId *ptr; Assert(TransactionIdIsValid(parent)); LWLockAcquire(SubtransControlLock, LW_EXCLUSIVE); slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid); ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno]; ptr += entryno; /* Current state should be 0 */ Assert(*ptr == InvalidTransactionId || (*ptr == parent && overwriteOK)); *ptr = parent; SubTransCtl->shared->page_dirty[slotno] = true; LWLockRelease(SubtransControlLock); }
/* * Record the parent of a subtransaction in the subtrans log. */ void SubTransSetParent(TransactionId xid, TransactionId parent) { int pageno = TransactionIdToPage(xid); int entryno = TransactionIdToEntry(xid); int slotno; TransactionId *ptr; Assert(TransactionIdIsValid(parent)); Assert(TransactionIdFollows(xid, parent)); LWLockAcquire(SubtransControlLock, LW_EXCLUSIVE); slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid); ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno]; ptr += entryno; /* * It's possible we'll try to set the parent xid multiple times but we * shouldn't ever be changing the xid from one valid xid to another valid * xid, which would corrupt the data structure. */ if (*ptr != parent) { Assert(*ptr == InvalidTransactionId); *ptr = parent; SubTransCtl->shared->page_dirty[slotno] = true; } LWLockRelease(SubtransControlLock); }
static Snapshot DtmGetSnapshot(Snapshot snapshot) { if (TransactionIdIsValid(DtmNextXid) && snapshot != &CatalogSnapshotData) { if (!DtmHasGlobalSnapshot && (snapshot != DtmLastSnapshot || DtmCurcid != GetCurrentCommandId(false))) { ArbiterGetSnapshot(DtmNextXid, &DtmSnapshot, &dtm->minXid); } DtmLastSnapshot = snapshot; DtmMergeWithGlobalSnapshot(snapshot); DtmCurcid = snapshot->curcid; if (!IsolationUsesXactSnapshot()) { /* Use single global snapshot during all transaction for repeatable read isolation level, * but obtain new global snapshot each time it is requested for read committed isolation level */ DtmHasGlobalSnapshot = false; } } else { /* For local transactions and catalog snapshots use default GetSnapshotData implementation */ snapshot = PgGetSnapshotData(snapshot); } DtmUpdateRecentXmin(snapshot); return snapshot; }
GlobalTransactionId BeginTranGTM(GTM_Timestamp *timestamp) { GlobalTransactionId xid = InvalidGlobalTransactionId; CheckConnection(); // TODO Isolation level if (conn) xid = begin_transaction(conn, GTM_ISOLATION_RC, timestamp); /* If something went wrong (timeout), try and reset GTM connection * and retry. This is safe at the beginning of a transaction. */ if (!TransactionIdIsValid(xid)) { CloseGTM(); InitGTM(); if (conn) xid = begin_transaction(conn, GTM_ISOLATION_RC, timestamp); } #ifdef XCP if (xid) IsXidFromGTM = true; #endif currentGxid = xid; return xid; }
/* * Check if our cached information about a datatype is still valid */ static bool PLy_procedure_argument_valid(PLyTypeInfo *arg) { HeapTuple relTup; bool valid; /* Nothing to cache unless type is composite */ if (arg->is_rowtype != 1) return true; /* * Zero typ_relid means that we got called on an output argument of a * function returning a unnamed record type; the info for it can't change. */ if (!OidIsValid(arg->typ_relid)) return true; /* Else we should have some cached data */ Assert(TransactionIdIsValid(arg->typrel_xmin)); Assert(ItemPointerIsValid(&arg->typrel_tid)); /* Get the pg_class tuple for the data type */ relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid)); if (!HeapTupleIsValid(relTup)) elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid); /* If it has changed, the cached data is not valid */ valid = (arg->typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) && ItemPointerEquals(&arg->typrel_tid, &relTup->t_self)); ReleaseSysCache(relTup); return valid; }
/* * Compute the oldest xmin across all slots and store it in the ProcArray. * * If already_locked is true, ProcArrayLock has already been acquired * exclusively. */ void ReplicationSlotsComputeRequiredXmin(bool already_locked) { int i; TransactionId agg_xmin = InvalidTransactionId; TransactionId agg_catalog_xmin = InvalidTransactionId; Assert(ReplicationSlotCtl != NULL); LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (i = 0; i < max_replication_slots; i++) { ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i]; TransactionId effective_xmin; TransactionId effective_catalog_xmin; if (!s->in_use) continue; { volatile ReplicationSlot *vslot = s; SpinLockAcquire(&s->mutex); effective_xmin = vslot->effective_xmin; effective_catalog_xmin = vslot->effective_catalog_xmin; SpinLockRelease(&s->mutex); } /* check the data xmin */ if (TransactionIdIsValid(effective_xmin) && (!TransactionIdIsValid(agg_xmin) || TransactionIdPrecedes(effective_xmin, agg_xmin))) agg_xmin = effective_xmin; /* check the catalog xmin */ if (TransactionIdIsValid(effective_catalog_xmin) && (!TransactionIdIsValid(agg_catalog_xmin) || TransactionIdPrecedes(effective_catalog_xmin, agg_catalog_xmin))) agg_catalog_xmin = effective_catalog_xmin; } LWLockRelease(ReplicationSlotControlLock); ProcArraySetReplicationSlotXmin(agg_xmin, agg_catalog_xmin, already_locked); }
void MMJoinTransaction(TransactionId xid) { if (TransactionIdIsValid(DtmNextXid)) elog(ERROR, "dtm_begin/join_transaction should be called only once for global transaction"); DtmNextXid = xid; if (!TransactionIdIsValid(DtmNextXid)) elog(ERROR, "Arbiter was not able to assign XID"); DtmVoted = false; ArbiterGetSnapshot(DtmNextXid, &DtmSnapshot, &dtm->minXid); XTM_INFO("%d: Join global transaction %d, dtm->minXid=%d\n", getpid(), DtmNextXid, dtm->minXid); DtmHasGlobalSnapshot = true; DtmLastSnapshot = NULL; MMIsDistributedTrans = true; MMMarkTransAsLocal(DtmNextXid); }
void MMBeginTransaction(void) { if (TransactionIdIsValid(DtmNextXid)) elog(ERROR, "MMBeginTransaction should be called only once for global transaction"); if (dtm == NULL) elog(ERROR, "DTM is not properly initialized, please check that pg_dtm plugin was added to shared_preload_libraries list in postgresql.conf"); Assert(!RecoveryInProgress()); XTM_INFO("%d: Try to start global transaction\n", getpid()); DtmNextXid = ArbiterStartTransaction(&DtmSnapshot, &dtm->minXid, dtm->nNodes); if (!TransactionIdIsValid(DtmNextXid)) elog(ERROR, "Arbiter was not able to assign XID"); XTM_INFO("%d: Start global transaction %d, dtm->minXid=%d\n", getpid(), DtmNextXid, dtm->minXid); DtmVoted = false; DtmHasGlobalSnapshot = true; DtmLastSnapshot = NULL; MMIsDistributedTrans = false; }
static void DtmSerializeLock(PROCLOCK* proclock, void* arg) { ByteBuffer* buf = (ByteBuffer*)arg; LOCK* lock = proclock->tag.myLock; PGPROC* proc = proclock->tag.myProc; if (lock != NULL) { PGXACT* srcPgXact = &ProcGlobal->allPgXact[proc->pgprocno]; if (TransactionIdIsValid(srcPgXact->xid) && proc->waitLock == lock) { LockMethod lockMethodTable = GetLocksMethodTable(lock); int numLockModes = lockMethodTable->numLockModes; int conflictMask = lockMethodTable->conflictTab[proc->waitLockMode]; SHM_QUEUE *procLocks = &(lock->procLocks); int lm; ByteBufferAppendInt32(buf, srcPgXact->xid); /* waiting transaction */ proclock = (PROCLOCK *) SHMQueueNext(procLocks, procLocks, offsetof(PROCLOCK, lockLink)); while (proclock) { if (proc != proclock->tag.myProc) { PGXACT* dstPgXact = &ProcGlobal->allPgXact[proclock->tag.myProc->pgprocno]; if (TransactionIdIsValid(dstPgXact->xid)) { Assert(srcPgXact->xid != dstPgXact->xid); for (lm = 1; lm <= numLockModes; lm++) { if ((proclock->holdMask & LOCKBIT_ON(lm)) && (conflictMask & LOCKBIT_ON(lm))) { XTM_INFO("%d: %u(%u) waits for %u(%u)\n", getpid(), srcPgXact->xid, proc->pid, dstPgXact->xid, proclock->tag.myProc->pid); ByteBufferAppendInt32(buf, dstPgXact->xid); /* transaction holding lock */ break; } } } } proclock = (PROCLOCK *) SHMQueueNext(procLocks, &proclock->lockLink, offsetof(PROCLOCK, lockLink)); } ByteBufferAppendInt32(buf, 0); /* end of lock owners list */ } } }
void MMMarkTransAsLocal(TransactionId xid) { LocalTransaction* lt; Assert(TransactionIdIsValid(xid)); LWLockAcquire(dtm->hashLock, LW_EXCLUSIVE); lt = hash_search(local_trans, &xid, HASH_ENTER, NULL); lt->count = dtm->nNodes-1; LWLockRelease(dtm->hashLock); }
/* * TransactionIdDidCommit * True iff transaction associated with the identifier did commit. * * Note: * Assumes transaction identifier is valid. */ bool /* true if given transaction committed */ TransactionIdDidCommit(TransactionId transactionId) { XidStatus xidstatus; xidstatus = TransactionLogFetch(transactionId); /* * If it's marked committed, it's committed. */ if (xidstatus == TRANSACTION_STATUS_COMMITTED) #ifdef PGXC { syncGXID_GTM((GlobalTransactionId)transactionId); #endif return true; #ifdef PGXC } #endif /* * If it's marked subcommitted, we have to check the parent recursively. * However, if it's older than TransactionXmin, we can't look at * pg_subtrans; instead assume that the parent crashed without cleaning up * its children. * * Originally we Assert'ed that the result of SubTransGetParent was not * zero. However with the introduction of prepared transactions, there can * be a window just after database startup where we do not have complete * knowledge in pg_subtrans of the transactions after TransactionXmin. * StartupSUBTRANS() has ensured that any missing information will be * zeroed. Since this case should not happen under normal conditions, it * seems reasonable to emit a WARNING for it. */ if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED) { TransactionId parentXid; if (TransactionIdPrecedes(transactionId, TransactionXmin)) return false; parentXid = SubTransGetParent(transactionId); if (!TransactionIdIsValid(parentXid)) { elog(WARNING, "no pg_subtrans entry for subcommitted XID %u", transactionId); return false; } return TransactionIdDidCommit(parentXid); } /* * It's not committed. */ return false; }
/* * Is the tuple really only locked? That is, is it not updated? * * It's easy to check just infomask bits if the locker is not a multi; but * otherwise we need to verify that the updating transaction has not aborted. * * This function is here because it follows the same time qualification rules * laid out at the top of this file. */ bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple) { TransactionId xmax; /* if there's no valid Xmax, then there's obviously no update either */ if (tuple->t_infomask & HEAP_XMAX_INVALID) return true; if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY) return true; /* invalid xmax means no update */ if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple))) return true; /* * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must * necessarily have been updated */ if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI)) return false; /* ... but if it's a multi, then perhaps the updating Xid aborted. */ xmax = HeapTupleGetUpdateXid(tuple); /* not LOCKED_ONLY, so it has to have an xmax */ Assert(TransactionIdIsValid(xmax)); if (TransactionIdIsCurrentTransactionId(xmax)) return false; if (TransactionIdIsInProgress(xmax)) return false; if (TransactionIdDidCommit(xmax)) return false; /* * not current, not in progress, not committed -- must have aborted or * crashed */ return true; }
static void DtmSetTransactionStatus(TransactionId xid, int nsubxids, TransactionId *subxids, XidStatus status, XLogRecPtr lsn) { XTM_INFO("%d: DtmSetTransactionStatus %u = %u\n", getpid(), xid, status); if (!RecoveryInProgress()) { if (TransactionIdIsValid(DtmNextXid)) { DtmVoted = true; if (status == TRANSACTION_STATUS_ABORTED || !MMIsDistributedTrans) { PgTransactionIdSetTreeStatus(xid, nsubxids, subxids, status, lsn); ArbiterSetTransStatus(xid, TRANSACTION_STATUS_ABORTED, false); XTM_INFO("Abort transaction %d\n", xid); return; } else { XidStatus verdict; XTM_INFO("Begin commit transaction %d\n", xid); /* Mark transaction as in-doubt in xid_in_doubt hash table */ LWLockAcquire(dtm->hashLock, LW_EXCLUSIVE); hash_search(xid_in_doubt, &DtmNextXid, HASH_ENTER, NULL); LWLockRelease(dtm->hashLock); verdict = ArbiterSetTransStatus(xid, status, true); if (verdict != status) { XTM_INFO("Commit of transaction %d is rejected by arbiter: staus=%d\n", xid, verdict); DtmNextXid = InvalidTransactionId; DtmLastSnapshot = NULL; MMIsDistributedTrans = false; MarkAsAborted(); END_CRIT_SECTION(); elog(ERROR, "Commit of transaction %d is rejected by DTM", xid); } else { XTM_INFO("Commit transaction %d\n", xid); } } } else { XTM_INFO("Set transaction %u status in local CLOG\n" , xid); } } else if (status != TRANSACTION_STATUS_ABORTED) { XidStatus gs; gs = ArbiterGetTransStatus(xid, false); if (gs != TRANSACTION_STATUS_UNKNOWN) { Assert(gs != TRANSACTION_STATUS_IN_PROGRESS); status = gs; } } PgTransactionIdSetTreeStatus(xid, nsubxids, subxids, status, lsn); }
/* * SubTransGetTopmostTransaction * * Returns the topmost transaction of the given transaction id. * * Because we cannot look back further than TransactionXmin, it is possible * that this function will lie and return an intermediate subtransaction ID * instead of the true topmost parent ID. This is OK, because in practice * we only care about detecting whether the topmost parent is still running * or is part of a current snapshot's list of still-running transactions. * Therefore, any XID before TransactionXmin is as good as any other. */ TransactionId SubTransGetTopmostTransaction(TransactionId xid) { TransactionId parentXid = xid, previousXid = xid; /* Can't ask about stuff that might not be around anymore */ Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin)); while (TransactionIdIsValid(parentXid)) { previousXid = parentXid; if (TransactionIdPrecedes(parentXid, TransactionXmin)) break; parentXid = SubTransGetParent(parentXid); } Assert(TransactionIdIsValid(previousXid)); return previousXid; }
/* Record lowest soon-prunable XID */ static void heap_prune_record_prunable(PruneState *prstate, TransactionId xid) { /* * This should exactly match the PageSetPrunable macro. We can't store * directly into the page header yet, so we update working state. */ Assert(TransactionIdIsNormal(xid)); if (!TransactionIdIsValid(prstate->new_prune_xid) || TransactionIdPrecedes(xid, prstate->new_prune_xid)) prstate->new_prune_xid = xid; }
/* * LockGXact * Locate the prepared transaction and mark it busy for COMMIT or PREPARE. */ static GlobalTransaction LockGXact(const char *gid, Oid user) { int i; LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); for (i = 0; i < TwoPhaseState->numPrepXacts; i++) { GlobalTransaction gxact = TwoPhaseState->prepXacts[i]; /* Ignore not-yet-valid GIDs */ if (!gxact->valid) continue; if (strcmp(gxact->gid, gid) != 0) continue; /* Found it, but has someone else got it locked? */ if (TransactionIdIsValid(gxact->locking_xid)) { if (TransactionIdIsActive(gxact->locking_xid)) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("prepared transaction with identifier \"%s\" is busy", gid))); gxact->locking_xid = InvalidTransactionId; } if (user != gxact->owner && !superuser_arg(user)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to finish prepared transaction"), errhint("Must be superuser or the user that prepared the transaction."))); /* OK for me to lock it */ gxact->locking_xid = GetTopTransactionId(); LWLockRelease(TwoPhaseStateLock); return gxact; } LWLockRelease(TwoPhaseStateLock); ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("prepared transaction with identifier \"%s\" does not exist", gid))); /* NOTREACHED */ return NULL; }
/* * SpeculativeInsertionWait * * Wait for the specified transaction to finish or abort the insertion of a * tuple. */ void SpeculativeInsertionWait(TransactionId xid, uint32 token) { LOCKTAG tag; SET_LOCKTAG_SPECULATIVE_INSERTION(tag, xid, token); Assert(TransactionIdIsValid(xid)); Assert(token != 0); (void) LockAcquire(&tag, ShareLock, false, false); LockRelease(&tag, ShareLock, false); }
/* * 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; }
/* * For Hot Standby we need to know the highest transaction id that will * be removed by any change. VACUUM proceeds in a number of passes so * we need to consider how each pass operates. The first phase runs * heap_page_prune(), which can issue XLOG_HEAP2_CLEAN records as it * progresses - these will have a latestRemovedXid on each record. * In some cases this removes all of the tuples to be removed, though * often we have dead tuples with index pointers so we must remember them * for removal in phase 3. Index records for those rows are removed * in phase 2 and index blocks do not have MVCC information attached. * So before we can allow removal of any index tuples we need to issue * a WAL record containing the latestRemovedXid of rows that will be * removed in phase three. This allows recovery queries to block at the * correct place, i.e. before phase two, rather than during phase three * which would be after the rows have become inaccessible. */ static void vacuum_log_cleanup_info(Relation rel, LVRelStats *vacrelstats) { /* * Skip this for relations for which no WAL is to be written, or if we're * not trying to support archive recovery. */ if (!RelationNeedsWAL(rel) || !XLogIsNeeded()) return; /* * No need to write the record at all unless it contains a valid value */ if (TransactionIdIsValid(vacrelstats->latestRemovedXid)) (void) log_heap_cleanup_info(rel->rd_node, vacrelstats->latestRemovedXid); }
/* * SetHintBits() * * Set commit/abort hint bits on a tuple, if appropriate at this time. * * It is only safe to set a transaction-committed hint bit if we know the * transaction's commit record has been flushed to disk, or if the table is * temporary or unlogged and will be obliterated by a crash anyway. We * cannot change the LSN of the page here because we may hold only a share * lock on the buffer, so we can't use the LSN to interlock this; we have to * just refrain from setting the hint bit until some future re-examination * of the tuple. * * We can always set hint bits when marking a transaction aborted. (Some * code in heapam.c relies on that!) * * Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then * we can always set the hint bits, since pre-9.0 VACUUM FULL always used * synchronous commits and didn't move tuples that weren't previously * hinted. (This is not known by this subroutine, but is applied by its * callers.) Note: old-style VACUUM FULL is gone, but we have to keep this * module's support for MOVED_OFF/MOVED_IN flag bits for as long as we * support in-place update from pre-9.0 databases. * * Normal commits may be asynchronous, so for those we need to get the LSN * of the transaction and then check whether this is flushed. * * The caller should pass xid as the XID of the transaction to check, or * InvalidTransactionId if no check is needed. */ static inline void SetHintBits(HeapTupleHeader tuple, Buffer buffer, uint16 infomask, TransactionId xid) { if (TransactionIdIsValid(xid)) { /* NB: xid must be known committed here! */ XLogRecPtr commitLSN = TransactionIdGetCommitLSN(xid); if (XLogNeedsFlush(commitLSN) && BufferIsPermanent(buffer)) return; /* not flushed yet, so don't set hint */ } tuple->t_infomask |= infomask; MarkBufferDirtyHint(buffer, true); }
/* * For Hot Standby we need to know the highest transaction id that will * be removed by any change. VACUUM proceeds in a number of passes so * we need to consider how each pass operates. The first phase runs * heap_page_prune(), which can issue XLOG_HEAP2_CLEAN records as it * progresses - these will have a latestRemovedXid on each record. * In some cases this removes all of the tuples to be removed, though * often we have dead tuples with index pointers so we must remember them * for removal in phase 3. Index records for those rows are removed * in phase 2 and index blocks do not have MVCC information attached. * So before we can allow removal of any index tuples we need to issue * a WAL record containing the latestRemovedXid of rows that will be * removed in phase three. This allows recovery queries to block at the * correct place, i.e. before phase two, rather than during phase three * which would be after the rows have become inaccessible. */ static void vacuum_log_cleanup_info(Relation rel, LVRelStats *vacrelstats) { /* * No need to log changes for temp tables, they do not contain data * visible on the standby server. */ if (rel->rd_istemp || !XLogIsNeeded()) return; /* * No need to write the record at all unless it contains a valid value */ if (TransactionIdIsValid(vacrelstats->latestRemovedXid)) (void) log_heap_cleanup_info(rel->rd_node, vacrelstats->latestRemovedXid); }
/* * XactLockTableWait * * Wait for the specified transaction to commit or abort. If an operation * is specified, an error context callback is set up. If 'oper' is passed as * None, no error context callback is set up. * * Note that this does the right thing for subtransactions: if we wait on a * subtransaction, we will exit as soon as it aborts or its top parent commits. * It takes some extra work to ensure this, because to save on shared memory * the XID lock of a subtransaction is released when it ends, whether * successfully or unsuccessfully. So we have to check if it's "still running" * and if so wait for its parent. */ void XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid, XLTW_Oper oper) { LOCKTAG tag; XactLockTableWaitInfo info; ErrorContextCallback callback; /* * If an operation is specified, set up our verbose error context * callback. */ if (oper != XLTW_None) { Assert(RelationIsValid(rel)); Assert(ItemPointerIsValid(ctid)); info.rel = rel; info.ctid = ctid; info.oper = oper; callback.callback = XactLockTableWaitErrorCb; callback.arg = &info; callback.previous = error_context_stack; error_context_stack = &callback; } for (;;) { Assert(TransactionIdIsValid(xid)); Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny())); SET_LOCKTAG_TRANSACTION(tag, xid); (void) LockAcquire(&tag, ShareLock, false, false); LockRelease(&tag, ShareLock, false); if (!TransactionIdIsInProgress(xid)) break; xid = SubTransGetParent(xid); } if (oper != XLTW_None) error_context_stack = callback.previous; }
/* * Get oldest Xid visible by any active transaction (global or local) * Take in account global Xmin received from DTMD */ static TransactionId DtmGetOldestXmin(Relation rel, bool ignoreVacuum) { TransactionId localXmin = PgGetOldestXmin(rel, ignoreVacuum); TransactionId globalXmin = dtm->minXid; XTM_INFO("XTM: DtmGetOldestXmin localXmin=%d, globalXmin=%d\n", localXmin, globalXmin); if (TransactionIdIsValid(globalXmin)) { globalXmin -= vacuum_defer_cleanup_age; if (!TransactionIdIsNormal(globalXmin)) globalXmin = FirstNormalTransactionId; if (TransactionIdPrecedes(globalXmin, localXmin)) localXmin = globalXmin; XTM_INFO("XTM: DtmGetOldestXmin adjusted localXmin=%d, globalXmin=%d\n", localXmin, globalXmin); } return localXmin; }
bool DtmDetectGlobalDeadLock(PGPROC* proc) { bool hasDeadlock = false; ByteBuffer buf; PGXACT* pgxact = &ProcGlobal->allPgXact[proc->pgprocno]; if (TransactionIdIsValid(pgxact->xid)) { ByteBufferAlloc(&buf); XTM_INFO("%d: wait graph begin\n", getpid()); EnumerateLocks(DtmSerializeLock, &buf); XTM_INFO("%d: wait graph end\n", getpid()); hasDeadlock = ArbiterDetectDeadLock(PostPortNumber, pgxact->xid, buf.data, buf.used); ByteBufferFree(&buf); XTM_INFO("%d: deadlock %sdetected for transaction %u\n", getpid(), hasDeadlock ? "": "not ", pgxact->xid); if (hasDeadlock) { elog(WARNING, "Deadlock detected for transaction %u", pgxact->xid); } } return hasDeadlock; }
/* * TransactionIdDidAbort * True iff transaction associated with the identifier did abort. * * Note: * Assumes transaction identifier is valid. */ bool /* true if given transaction aborted */ TransactionIdDidAbort(TransactionId transactionId) { XidStatus xidstatus; xidstatus = TransactionLogFetch(transactionId); /* * If it's marked aborted, it's aborted. */ if (xidstatus == TRANSACTION_STATUS_ABORTED) return true; /* * If it's marked subcommitted, we have to check the parent recursively. * However, if it's older than TransactionXmin, we can't look at * pg_subtrans; instead assume that the parent crashed without cleaning up * its children. */ if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED) { TransactionId parentXid; if (TransactionIdPrecedes(transactionId, TransactionXmin)) return true; parentXid = SubTransGetParent(transactionId); if (!TransactionIdIsValid(parentXid)) { /* see notes in TransactionIdDidCommit */ elog(WARNING, "no pg_subtrans entry for subcommitted XID %u", transactionId); return true; } return TransactionIdDidAbort(parentXid); } /* * It's not aborted. */ return false; }
/* ---------------- * index_getnext_tid - get the next TID from a scan * * The result is the next TID satisfying the scan keys, * or NULL if no more matching tuples exist. * ---------------- */ ItemPointer index_getnext_tid(IndexScanDesc scan, ScanDirection direction) { FmgrInfo *procedure; bool found; SCAN_CHECKS; GET_SCAN_PROCEDURE(amgettuple); Assert(TransactionIdIsValid(RecentGlobalXmin)); /* * The AM's amgettuple proc finds the next index entry matching the scan * keys, and puts the TID into scan->xs_ctup.t_self. It should also set * scan->xs_recheck and possibly scan->xs_itup, though we pay no attention * to those fields here. */ found = DatumGetBool(FunctionCall2(procedure, PointerGetDatum(scan), Int32GetDatum(direction))); /* Reset kill flag immediately for safety */ scan->kill_prior_tuple = false; /* If we're out of index entries, we're done */ if (!found) { /* ... but first, release any held pin on a heap page */ if (BufferIsValid(scan->xs_cbuf)) { ReleaseBuffer(scan->xs_cbuf); scan->xs_cbuf = InvalidBuffer; } return NULL; } pgstat_count_index_tuples(scan->indexRelation, 1); /* Return the TID of the tuple we found. */ return &scan->xs_ctup.t_self; }
/* * XactLockTableWait * * Wait for the specified transaction to commit or abort. * * Note that this does the right thing for subtransactions: if we wait on a * subtransaction, we will exit as soon as it aborts or its top parent commits. * It takes some extra work to ensure this, because to save on shared memory * the XID lock of a subtransaction is released when it ends, whether * successfully or unsuccessfully. So we have to check if it's "still running" * and if so wait for its parent. */ void XactLockTableWait(TransactionId xid) { LOCKTAG tag; for (;;) { Assert(TransactionIdIsValid(xid)); Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny())); SET_LOCKTAG_TRANSACTION(tag, xid); (void) LockAcquire(&tag, ShareLock, false, false); LockRelease(&tag, ShareLock, false); if (!TransactionIdIsInProgress(xid)) break; xid = SubTransGetParent(xid); } }
void ResolveRecoveryConflictWithSnapshot(TransactionId latestRemovedXid, RelFileNode node) { VirtualTransactionId *backends; /* * If we get passed InvalidTransactionId then we are a little surprised, * but it is theoretically possible in normal running. It also happens * when replaying already applied WAL records after a standby crash or * restart. If latestRemovedXid is invalid then there is no conflict. That * rule applies across all record types that suffer from this conflict. */ if (!TransactionIdIsValid(latestRemovedXid)) return; backends = GetConflictingVirtualXIDs(latestRemovedXid, node.dbNode); ResolveRecoveryConflictWithVirtualXIDs(backends, PROCSIG_RECOVERY_CONFLICT_SNAPSHOT); }
/* * Update local Recent*Xmin variables taken in account MinXmin received from DTMD */ static void DtmUpdateRecentXmin(Snapshot snapshot) { TransactionId xmin = dtm->minXid; XTM_INFO("XTM: DtmUpdateRecentXmin global xmin=%d, snapshot xmin %d\n", dtm->minXid, DtmSnapshot.xmin); if (TransactionIdIsValid(xmin)) { xmin -= vacuum_defer_cleanup_age; if (!TransactionIdIsNormal(xmin)) xmin = FirstNormalTransactionId; if (TransactionIdFollows(RecentGlobalDataXmin, xmin)) RecentGlobalDataXmin = xmin; if (TransactionIdFollows(RecentGlobalXmin, xmin)) RecentGlobalXmin = xmin; } if (TransactionIdFollows(RecentXmin, snapshot->xmin)) { ProcArrayInstallImportedXmin(snapshot->xmin, GetCurrentTransactionId()); RecentXmin = snapshot->xmin; } }
GlobalTransactionId BeginTranAutovacuumGTM(void) { GlobalTransactionId xid = InvalidGlobalTransactionId; CheckConnection(); // TODO Isolation level if (conn) xid = begin_transaction_autovacuum(conn, GTM_ISOLATION_RC); /* * If something went wrong (timeout), try and reset GTM connection and retry. * This is safe at the beginning of a transaction. */ if (!TransactionIdIsValid(xid)) { CloseGTM(); InitGTM(); if (conn) xid = begin_transaction_autovacuum(conn, GTM_ISOLATION_RC); } return xid; }