mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
Make release of 2PC identifier and locks consistent in COMMIT PREPARED
When preparing a transaction in two-phase commit, a dummy PGPROC entry holding the GID used for the transaction is registered, which gets released once COMMIT PREPARED is run. Prior releasing its shared memory state, all the locks taken in the prepared transaction are released using a dedicated set of callbacks (pgstat and multixact having similar callbacks), which may cause the locks to be released before the GID is set free. Hence, there is a small window where lock conflicts could happen, for example: - Transaction A releases its locks, still holding its GID in shared memory. - Transaction B held a lock which conflicted with locks of transaction A. - Transaction B continues its processing, reusing the same GID as transaction A. - Transaction B fails because of a conflicting GID, already in use by transaction A. This commit changes the shared memory state release so as post-commit callbacks and predicate lock cleanup happen consistently with the shared memory state cleanup for the dummy PGPROC entry. The race window is small and 2PC had this issue from the start, so no backpatch is done. On top if that fixes discussed involved ABI breakages, which are not welcome in stable branches. Reported-by: Oleksii Kliukin, Ildar Musin Diagnosed-by: Oleksii Kliukin, Ildar Musin Author: Michael Paquier Reviewed-by: Masahiko Sawada, Oleksii Kliukin Discussion: https://postgr.es/m/BF9B38A4-2BFF-46E8-BA87-A2D00A8047A6@hintbits.com
This commit is contained in:
parent
29ddb548f6
commit
effe7d9552
@ -1713,7 +1713,7 @@ PostPrepare_MultiXact(TransactionId xid)
|
||||
myOldestMember = OldestMemberMXactId[MyBackendId];
|
||||
if (MultiXactIdIsValid(myOldestMember))
|
||||
{
|
||||
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid);
|
||||
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid, false);
|
||||
|
||||
/*
|
||||
* Even though storing MultiXactId is atomic, acquire lock to make
|
||||
@ -1755,7 +1755,7 @@ void
|
||||
multixact_twophase_recover(TransactionId xid, uint16 info,
|
||||
void *recdata, uint32 len)
|
||||
{
|
||||
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid);
|
||||
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid, false);
|
||||
MultiXactId oldestMember;
|
||||
|
||||
/*
|
||||
@ -1776,7 +1776,7 @@ void
|
||||
multixact_twophase_postcommit(TransactionId xid, uint16 info,
|
||||
void *recdata, uint32 len)
|
||||
{
|
||||
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid);
|
||||
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid, true);
|
||||
|
||||
Assert(len == sizeof(MultiXactId));
|
||||
|
||||
|
@ -801,9 +801,12 @@ pg_prepared_xact(PG_FUNCTION_ARGS)
|
||||
* TwoPhaseGetGXact
|
||||
* Get the GlobalTransaction struct for a prepared transaction
|
||||
* specified by XID
|
||||
*
|
||||
* If lock_held is set to true, TwoPhaseStateLock will not be taken, so the
|
||||
* caller had better hold it.
|
||||
*/
|
||||
static GlobalTransaction
|
||||
TwoPhaseGetGXact(TransactionId xid)
|
||||
TwoPhaseGetGXact(TransactionId xid, bool lock_held)
|
||||
{
|
||||
GlobalTransaction result = NULL;
|
||||
int i;
|
||||
@ -811,6 +814,8 @@ TwoPhaseGetGXact(TransactionId xid)
|
||||
static TransactionId cached_xid = InvalidTransactionId;
|
||||
static GlobalTransaction cached_gxact = NULL;
|
||||
|
||||
Assert(!lock_held || LWLockHeldByMe(TwoPhaseStateLock));
|
||||
|
||||
/*
|
||||
* During a recovery, COMMIT PREPARED, or ABORT PREPARED, we'll be called
|
||||
* repeatedly for the same XID. We can save work with a simple cache.
|
||||
@ -818,7 +823,8 @@ TwoPhaseGetGXact(TransactionId xid)
|
||||
if (xid == cached_xid)
|
||||
return cached_gxact;
|
||||
|
||||
LWLockAcquire(TwoPhaseStateLock, LW_SHARED);
|
||||
if (!lock_held)
|
||||
LWLockAcquire(TwoPhaseStateLock, LW_SHARED);
|
||||
|
||||
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
|
||||
{
|
||||
@ -832,7 +838,8 @@ TwoPhaseGetGXact(TransactionId xid)
|
||||
}
|
||||
}
|
||||
|
||||
LWLockRelease(TwoPhaseStateLock);
|
||||
if (!lock_held)
|
||||
LWLockRelease(TwoPhaseStateLock);
|
||||
|
||||
if (result == NULL) /* should not happen */
|
||||
elog(ERROR, "failed to find GlobalTransaction for xid %u", xid);
|
||||
@ -849,12 +856,13 @@ TwoPhaseGetGXact(TransactionId xid)
|
||||
*
|
||||
* Dummy backend IDs are similar to real backend IDs of real backends.
|
||||
* They start at MaxBackends + 1, and are unique across all currently active
|
||||
* real backends and prepared transactions.
|
||||
* real backends and prepared transactions. If lock_held is set to true,
|
||||
* TwoPhaseStateLock will not be taken, so the caller had better hold it.
|
||||
*/
|
||||
BackendId
|
||||
TwoPhaseGetDummyBackendId(TransactionId xid)
|
||||
TwoPhaseGetDummyBackendId(TransactionId xid, bool lock_held)
|
||||
{
|
||||
GlobalTransaction gxact = TwoPhaseGetGXact(xid);
|
||||
GlobalTransaction gxact = TwoPhaseGetGXact(xid, lock_held);
|
||||
|
||||
return gxact->dummyBackendId;
|
||||
}
|
||||
@ -862,11 +870,14 @@ TwoPhaseGetDummyBackendId(TransactionId xid)
|
||||
/*
|
||||
* TwoPhaseGetDummyProc
|
||||
* Get the PGPROC that represents a prepared transaction specified by XID
|
||||
*
|
||||
* If lock_held is set to true, TwoPhaseStateLock will not be taken, so the
|
||||
* caller had better hold it.
|
||||
*/
|
||||
PGPROC *
|
||||
TwoPhaseGetDummyProc(TransactionId xid)
|
||||
TwoPhaseGetDummyProc(TransactionId xid, bool lock_held)
|
||||
{
|
||||
GlobalTransaction gxact = TwoPhaseGetGXact(xid);
|
||||
GlobalTransaction gxact = TwoPhaseGetGXact(xid, lock_held);
|
||||
|
||||
return &ProcGlobal->allProcs[gxact->pgprocno];
|
||||
}
|
||||
@ -1560,6 +1571,14 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
|
||||
if (hdr->initfileinval)
|
||||
RelationCacheInitFilePostInvalidate();
|
||||
|
||||
/*
|
||||
* Acquire the two-phase lock. We want to work on the two-phase callbacks
|
||||
* while holding it to avoid potential conflicts with other transactions
|
||||
* attempting to use the same GID, so the lock is released once the shared
|
||||
* memory state is cleared.
|
||||
*/
|
||||
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
|
||||
|
||||
/* And now do the callbacks */
|
||||
if (isCommit)
|
||||
ProcessRecords(bufptr, xid, twophase_postcommit_callbacks);
|
||||
@ -1568,6 +1587,15 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
|
||||
|
||||
PredicateLockTwoPhaseFinish(xid, isCommit);
|
||||
|
||||
/* Clear shared memory state */
|
||||
RemoveGXact(gxact);
|
||||
|
||||
/*
|
||||
* Release the lock as all callbacks are called and shared memory cleanup
|
||||
* is done.
|
||||
*/
|
||||
LWLockRelease(TwoPhaseStateLock);
|
||||
|
||||
/* Count the prepared xact as committed or aborted */
|
||||
AtEOXact_PgStat(isCommit);
|
||||
|
||||
@ -1577,9 +1605,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
|
||||
if (gxact->ondisk)
|
||||
RemoveTwoPhaseFile(xid, true);
|
||||
|
||||
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
|
||||
RemoveGXact(gxact);
|
||||
LWLockRelease(TwoPhaseStateLock);
|
||||
MyLockedGxact = NULL;
|
||||
|
||||
RESUME_INTERRUPTS();
|
||||
|
@ -3243,7 +3243,7 @@ AtPrepare_Locks(void)
|
||||
void
|
||||
PostPrepare_Locks(TransactionId xid)
|
||||
{
|
||||
PGPROC *newproc = TwoPhaseGetDummyProc(xid);
|
||||
PGPROC *newproc = TwoPhaseGetDummyProc(xid, false);
|
||||
HASH_SEQ_STATUS status;
|
||||
LOCALLOCK *locallock;
|
||||
LOCK *lock;
|
||||
@ -4034,7 +4034,7 @@ lock_twophase_recover(TransactionId xid, uint16 info,
|
||||
void *recdata, uint32 len)
|
||||
{
|
||||
TwoPhaseLockRecord *rec = (TwoPhaseLockRecord *) recdata;
|
||||
PGPROC *proc = TwoPhaseGetDummyProc(xid);
|
||||
PGPROC *proc = TwoPhaseGetDummyProc(xid, false);
|
||||
LOCKTAG *locktag;
|
||||
LOCKMODE lockmode;
|
||||
LOCKMETHODID lockmethodid;
|
||||
@ -4247,7 +4247,7 @@ lock_twophase_postcommit(TransactionId xid, uint16 info,
|
||||
void *recdata, uint32 len)
|
||||
{
|
||||
TwoPhaseLockRecord *rec = (TwoPhaseLockRecord *) recdata;
|
||||
PGPROC *proc = TwoPhaseGetDummyProc(xid);
|
||||
PGPROC *proc = TwoPhaseGetDummyProc(xid, true);
|
||||
LOCKTAG *locktag;
|
||||
LOCKMETHODID lockmethodid;
|
||||
LockMethod lockMethodTable;
|
||||
|
@ -34,8 +34,8 @@ extern void TwoPhaseShmemInit(void);
|
||||
extern void AtAbort_Twophase(void);
|
||||
extern void PostPrepare_Twophase(void);
|
||||
|
||||
extern PGPROC *TwoPhaseGetDummyProc(TransactionId xid);
|
||||
extern BackendId TwoPhaseGetDummyBackendId(TransactionId xid);
|
||||
extern PGPROC *TwoPhaseGetDummyProc(TransactionId xid, bool lock_held);
|
||||
extern BackendId TwoPhaseGetDummyBackendId(TransactionId xid, bool lock_held);
|
||||
|
||||
extern GlobalTransaction MarkAsPreparing(TransactionId xid, const char *gid,
|
||||
TimestampTz prepared_at,
|
||||
|
Loading…
x
Reference in New Issue
Block a user