mirror of
https://github.com/postgres/postgres.git
synced 2025-09-02 04:21:28 +03:00
Fix CREATE INDEX CONCURRENTLY for the newest prepared transactions.
The purpose of commit 8a54e12a38
was to
fix this, and it sufficed when the PREPARE TRANSACTION completed before
the CIC looked for lock conflicts. Otherwise, things still broke. As
before, in a cluster having used CIC while having enabled prepared
transactions, queries that use the resulting index can silently fail to
find rows. It may be necessary to reindex to recover from past
occurrences; REINDEX CONCURRENTLY suffices. Fix this for future index
builds by making CIC wait for arbitrarily-recent prepared transactions
and for ordinary transactions that may yet PREPARE TRANSACTION. As part
of that, have PREPARE TRANSACTION transfer locks to its dummy PGPROC
before it calls ProcArrayClearTransaction(). Back-patch to 9.6 (all
supported versions).
Andrey Borodin, reviewed (in earlier versions) by Andres Freund.
Discussion: https://postgr.es/m/01824242-AA92-4FE9-9BA7-AEBAFFEA3D0C@yandex-team.ru
This commit is contained in:
@@ -871,9 +871,10 @@ XactLockTableWaitErrorCb(void *arg)
|
||||
* To do this, obtain the current list of lockers, and wait on their VXIDs
|
||||
* until they are finished.
|
||||
*
|
||||
* Note we don't try to acquire the locks on the given locktags, only the VXIDs
|
||||
* of its lock holders; if somebody grabs a conflicting lock on the objects
|
||||
* after we obtained our initial list of lockers, we will not wait for them.
|
||||
* Note we don't try to acquire the locks on the given locktags, only the
|
||||
* VXIDs and XIDs of their lock holders; if somebody grabs a conflicting lock
|
||||
* on the objects after we obtained our initial list of lockers, we will not
|
||||
* wait for them.
|
||||
*/
|
||||
void
|
||||
WaitForLockersMultiple(List *locktags, LOCKMODE lockmode, bool progress)
|
||||
|
@@ -2900,8 +2900,12 @@ FastPathGetRelationLockEntry(LOCALLOCK *locallock)
|
||||
* The result array is palloc'd and is terminated with an invalid VXID.
|
||||
* *countp, if not null, is updated to the number of items set.
|
||||
*
|
||||
* Of course, the result could be out of date by the time it's returned,
|
||||
* so use of this function has to be thought about carefully.
|
||||
* Of course, the result could be out of date by the time it's returned, so
|
||||
* use of this function has to be thought about carefully. Similarly, a
|
||||
* PGPROC with no "lxid" will be considered non-conflicting regardless of any
|
||||
* lock it holds. Existing callers don't care about a locker after that
|
||||
* locker's pg_xact updates complete. CommitTransaction() clears "lxid" after
|
||||
* pg_xact updates and before releasing locks.
|
||||
*
|
||||
* Note we never include the current xact's vxid in the result array,
|
||||
* since an xact never blocks itself.
|
||||
@@ -4529,37 +4533,80 @@ VirtualXactLockTableCleanup(void)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* XactLockForVirtualXact
|
||||
*
|
||||
* If TransactionIdIsValid(xid), this is essentially XactLockTableWait(xid,
|
||||
* NULL, NULL, XLTW_None) or ConditionalXactLockTableWait(xid). Unlike those
|
||||
* functions, it assumes "xid" is never a subtransaction and that "xid" is
|
||||
* prepared, committed, or aborted.
|
||||
*
|
||||
* If !TransactionIdIsValid(xid), this locks every prepared XID having been
|
||||
* known as "vxid" before its PREPARE TRANSACTION.
|
||||
*/
|
||||
static bool
|
||||
XactLockForVirtualXact(VirtualTransactionId vxid,
|
||||
TransactionId xid, bool wait)
|
||||
{
|
||||
bool more = false;
|
||||
|
||||
/* There is no point to wait for 2PCs if you have no 2PCs. */
|
||||
if (max_prepared_xacts == 0)
|
||||
return true;
|
||||
|
||||
do
|
||||
{
|
||||
LockAcquireResult lar;
|
||||
LOCKTAG tag;
|
||||
|
||||
/* Clear state from previous iterations. */
|
||||
if (more)
|
||||
{
|
||||
xid = InvalidTransactionId;
|
||||
more = false;
|
||||
}
|
||||
|
||||
/* If we have no xid, try to find one. */
|
||||
if (!TransactionIdIsValid(xid))
|
||||
xid = TwoPhaseGetXidByVirtualXID(vxid, &more);
|
||||
if (!TransactionIdIsValid(xid))
|
||||
{
|
||||
Assert(!more);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check or wait for XID completion. */
|
||||
SET_LOCKTAG_TRANSACTION(tag, xid);
|
||||
lar = LockAcquire(&tag, ShareLock, false, !wait);
|
||||
if (lar == LOCKACQUIRE_NOT_AVAIL)
|
||||
return false;
|
||||
LockRelease(&tag, ShareLock, false);
|
||||
} while (more);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* VirtualXactLock
|
||||
*
|
||||
* If wait = true, wait until the given VXID has been released, and then
|
||||
* return true.
|
||||
* If wait = true, wait as long as the given VXID or any XID acquired by the
|
||||
* same transaction is still running. Then, return true.
|
||||
*
|
||||
* If wait = false, just check whether the VXID is still running, and return
|
||||
* true or false.
|
||||
* If wait = false, just check whether that VXID or one of those XIDs is still
|
||||
* running, and return true or false.
|
||||
*/
|
||||
bool
|
||||
VirtualXactLock(VirtualTransactionId vxid, bool wait)
|
||||
{
|
||||
LOCKTAG tag;
|
||||
PGPROC *proc;
|
||||
TransactionId xid = InvalidTransactionId;
|
||||
|
||||
Assert(VirtualTransactionIdIsValid(vxid));
|
||||
|
||||
if (VirtualTransactionIdIsPreparedXact(vxid))
|
||||
{
|
||||
LockAcquireResult lar;
|
||||
|
||||
/*
|
||||
* Prepared transactions don't hold vxid locks. The
|
||||
* LocalTransactionId is always a normal, locked XID.
|
||||
*/
|
||||
SET_LOCKTAG_TRANSACTION(tag, vxid.localTransactionId);
|
||||
lar = LockAcquire(&tag, ShareLock, false, !wait);
|
||||
if (lar != LOCKACQUIRE_NOT_AVAIL)
|
||||
LockRelease(&tag, ShareLock, false);
|
||||
return lar != LOCKACQUIRE_NOT_AVAIL;
|
||||
}
|
||||
if (VirtualTransactionIdIsRecoveredPreparedXact(vxid))
|
||||
/* no vxid lock; localTransactionId is a normal, locked XID */
|
||||
return XactLockForVirtualXact(vxid, vxid.localTransactionId, wait);
|
||||
|
||||
SET_LOCKTAG_VIRTUALTRANSACTION(tag, vxid);
|
||||
|
||||
@@ -4573,7 +4620,7 @@ VirtualXactLock(VirtualTransactionId vxid, bool wait)
|
||||
*/
|
||||
proc = BackendIdGetProc(vxid.backendId);
|
||||
if (proc == NULL)
|
||||
return true;
|
||||
return XactLockForVirtualXact(vxid, InvalidTransactionId, wait);
|
||||
|
||||
/*
|
||||
* We must acquire this lock before checking the backendId and lxid
|
||||
@@ -4582,12 +4629,12 @@ VirtualXactLock(VirtualTransactionId vxid, bool wait)
|
||||
*/
|
||||
LWLockAcquire(&proc->fpInfoLock, LW_EXCLUSIVE);
|
||||
|
||||
/* If the transaction has ended, our work here is done. */
|
||||
if (proc->backendId != vxid.backendId
|
||||
|| proc->fpLocalTransactionId != vxid.localTransactionId)
|
||||
{
|
||||
/* VXID ended */
|
||||
LWLockRelease(&proc->fpInfoLock);
|
||||
return true;
|
||||
return XactLockForVirtualXact(vxid, InvalidTransactionId, wait);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -4634,6 +4681,16 @@ VirtualXactLock(VirtualTransactionId vxid, bool wait)
|
||||
proc->fpVXIDLock = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the proc has an XID now, we'll avoid a TwoPhaseGetXidByVirtualXID()
|
||||
* search. The proc might have assigned this XID but not yet locked it,
|
||||
* in which case the proc will lock this XID before releasing the VXID.
|
||||
* The fpInfoLock critical section excludes VirtualXactLockTableCleanup(),
|
||||
* so we won't save an XID of a different VXID. It doesn't matter whether
|
||||
* we save this before or after setting up the primary lock table entry.
|
||||
*/
|
||||
xid = proc->xid;
|
||||
|
||||
/* Done with proc->fpLockBits */
|
||||
LWLockRelease(&proc->fpInfoLock);
|
||||
|
||||
@@ -4641,7 +4698,7 @@ VirtualXactLock(VirtualTransactionId vxid, bool wait)
|
||||
(void) LockAcquire(&tag, ShareLock, false, false);
|
||||
|
||||
LockRelease(&tag, ShareLock, false);
|
||||
return true;
|
||||
return XactLockForVirtualXact(vxid, xid, wait);
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user