/* * 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); /* * By convention the parent xid gets allocated first, so should always * precede the child xid. Anything else points to a corrupted data * structure that could lead to an infinite loop, so exit. */ if (!TransactionIdPrecedes(parentXid, previousXid)) elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u", previousXid, parentXid); } Assert(TransactionIdIsValid(previousXid)); return previousXid; }
/* * 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; }
/* * 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; }
/* * 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; }
/* * 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); } }
/* * 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; }
/* * ConditionalXactLockTableWait * * As above, but only lock if we can get the lock without blocking. * Returns TRUE if the lock was acquired. */ bool ConditionalXactLockTableWait(TransactionId xid) { LOCKTAG tag; for (;;) { Assert(TransactionIdIsValid(xid)); Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny())); SET_LOCKTAG_TRANSACTION(tag, xid); if (LockAcquire(&tag, ShareLock, false, true) == LOCKACQUIRE_NOT_AVAIL) return false; LockRelease(&tag, ShareLock, false); if (!TransactionIdIsInProgress(xid)) break; xid = SubTransGetParent(xid); } return true; }