mirror of
https://github.com/postgres/postgres.git
synced 2025-11-06 07:49:08 +03:00
Prevent very-low-probability PANIC during PREPARE TRANSACTION.
The code in PostPrepare_Locks supposed that it could reassign locks to the prepared transaction's dummy PGPROC by deleting the PROCLOCK table entries and immediately creating new ones. This was safe when that code was written, but since we invented partitioning of the shared lock table, it's not safe --- another process could steal away the PROCLOCK entry in the short interval when it's on the freelist. Then, if we were otherwise out of shared memory, PostPrepare_Locks would have to PANIC, since it's too late to back out of the PREPARE at that point. Fix by inventing a dynahash.c function to atomically update a hashtable entry's key. (This might possibly have other uses in future.) This is an ancient bug that in principle we ought to back-patch, but the odds of someone hitting it in the field seem really tiny, because (a) the risk window is small, and (b) nobody runs servers with maxed-out lock tables for long, because they'll be getting non-PANIC out-of-memory errors anyway. So fixing it in HEAD seems sufficient, at least until the new code has gotten some testing.
This commit is contained in:
@@ -3028,7 +3028,6 @@ PostPrepare_Locks(TransactionId xid)
|
||||
LOCK *lock;
|
||||
PROCLOCK *proclock;
|
||||
PROCLOCKTAG proclocktag;
|
||||
bool found;
|
||||
int partition;
|
||||
|
||||
/* This is a critical section: any error means big trouble */
|
||||
@@ -3114,10 +3113,8 @@ PostPrepare_Locks(TransactionId xid)
|
||||
while (proclock)
|
||||
{
|
||||
PROCLOCK *nextplock;
|
||||
LOCKMASK holdMask;
|
||||
PROCLOCK *newproclock;
|
||||
|
||||
/* Get link first, since we may unlink/delete this proclock */
|
||||
/* Get link first, since we may unlink/relink this proclock */
|
||||
nextplock = (PROCLOCK *)
|
||||
SHMQueueNext(procLocks, &proclock->procLink,
|
||||
offsetof(PROCLOCK, procLink));
|
||||
@@ -3145,64 +3142,42 @@ PostPrepare_Locks(TransactionId xid)
|
||||
if (proclock->releaseMask != proclock->holdMask)
|
||||
elog(PANIC, "we seem to have dropped a bit somewhere");
|
||||
|
||||
holdMask = proclock->holdMask;
|
||||
|
||||
/*
|
||||
* We cannot simply modify proclock->tag.myProc to reassign
|
||||
* ownership of the lock, because that's part of the hash key and
|
||||
* the proclock would then be in the wrong hash chain. So, unlink
|
||||
* and delete the old proclock; create a new one with the right
|
||||
* contents; and link it into place. We do it in this order to be
|
||||
* certain we won't run out of shared memory (the way dynahash.c
|
||||
* works, the deleted object is certain to be available for
|
||||
* reallocation).
|
||||
* the proclock would then be in the wrong hash chain. Instead
|
||||
* use hash_update_hash_key. (We used to create a new hash entry,
|
||||
* but that risks out-of-memory failure if other processes are
|
||||
* busy making proclocks too.) We must unlink the proclock from
|
||||
* our procLink chain and put it into the new proc's chain, too.
|
||||
*
|
||||
* Note: the updated proclock hash key will still belong to the
|
||||
* same hash partition, cf proclock_hash(). So the partition
|
||||
* lock we already hold is sufficient for this.
|
||||
*/
|
||||
SHMQueueDelete(&proclock->lockLink);
|
||||
SHMQueueDelete(&proclock->procLink);
|
||||
if (!hash_search(LockMethodProcLockHash,
|
||||
(void *) &(proclock->tag),
|
||||
HASH_REMOVE, NULL))
|
||||
elog(PANIC, "proclock table corrupted");
|
||||
|
||||
/*
|
||||
* Create the hash key for the new proclock table.
|
||||
* Create the new hash key for the proclock.
|
||||
*/
|
||||
proclocktag.myLock = lock;
|
||||
proclocktag.myProc = newproc;
|
||||
|
||||
newproclock = (PROCLOCK *) hash_search(LockMethodProcLockHash,
|
||||
(void *) &proclocktag,
|
||||
HASH_ENTER_NULL, &found);
|
||||
if (!newproclock)
|
||||
ereport(PANIC, /* should not happen */
|
||||
(errcode(ERRCODE_OUT_OF_MEMORY),
|
||||
errmsg("out of shared memory"),
|
||||
errdetail("Not enough memory for reassigning the prepared transaction's locks.")));
|
||||
|
||||
/*
|
||||
* If new, initialize the new entry
|
||||
* Update the proclock. We should not find any existing entry
|
||||
* for the same hash key, since there can be only one entry for
|
||||
* any given lock with my own proc.
|
||||
*/
|
||||
if (!found)
|
||||
{
|
||||
newproclock->holdMask = 0;
|
||||
newproclock->releaseMask = 0;
|
||||
/* Add new proclock to appropriate lists */
|
||||
SHMQueueInsertBefore(&lock->procLocks, &newproclock->lockLink);
|
||||
SHMQueueInsertBefore(&(newproc->myProcLocks[partition]),
|
||||
&newproclock->procLink);
|
||||
PROCLOCK_PRINT("PostPrepare_Locks: new", newproclock);
|
||||
}
|
||||
else
|
||||
{
|
||||
PROCLOCK_PRINT("PostPrepare_Locks: found", newproclock);
|
||||
Assert((newproclock->holdMask & ~lock->grantMask) == 0);
|
||||
}
|
||||
if (!hash_update_hash_key(LockMethodProcLockHash,
|
||||
(void *) proclock,
|
||||
(void *) &proclocktag))
|
||||
elog(PANIC, "duplicate entry found while reassigning a prepared transaction's locks");
|
||||
|
||||
/*
|
||||
* Pass over the identified lock ownership.
|
||||
*/
|
||||
Assert((newproclock->holdMask & holdMask) == 0);
|
||||
newproclock->holdMask |= holdMask;
|
||||
/* Re-link into the new proc's proclock list */
|
||||
SHMQueueInsertBefore(&(newproc->myProcLocks[partition]),
|
||||
&proclock->procLink);
|
||||
|
||||
PROCLOCK_PRINT("PostPrepare_Locks: updated", proclock);
|
||||
|
||||
next_item:
|
||||
proclock = nextplock;
|
||||
|
||||
Reference in New Issue
Block a user