Skip to content

Commit effe7d9

Browse files
committed
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
1 parent 29ddb54 commit effe7d9

File tree

4 files changed

+44
-19
lines changed

4 files changed

+44
-19
lines changed

src/backend/access/transam/multixact.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,7 +1713,7 @@ PostPrepare_MultiXact(TransactionId xid)
17131713
myOldestMember = OldestMemberMXactId[MyBackendId];
17141714
if (MultiXactIdIsValid(myOldestMember))
17151715
{
1716-
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid);
1716+
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid, false);
17171717

17181718
/*
17191719
* Even though storing MultiXactId is atomic, acquire lock to make
@@ -1755,7 +1755,7 @@ void
17551755
multixact_twophase_recover(TransactionId xid, uint16 info,
17561756
void *recdata, uint32 len)
17571757
{
1758-
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid);
1758+
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid, false);
17591759
MultiXactId oldestMember;
17601760

17611761
/*
@@ -1776,7 +1776,7 @@ void
17761776
multixact_twophase_postcommit(TransactionId xid, uint16 info,
17771777
void *recdata, uint32 len)
17781778
{
1779-
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid);
1779+
BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid, true);
17801780

17811781
Assert(len == sizeof(MultiXactId));
17821782

src/backend/access/transam/twophase.c

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -801,24 +801,30 @@ pg_prepared_xact(PG_FUNCTION_ARGS)
801801
* TwoPhaseGetGXact
802802
* Get the GlobalTransaction struct for a prepared transaction
803803
* specified by XID
804+
*
805+
* If lock_held is set to true, TwoPhaseStateLock will not be taken, so the
806+
* caller had better hold it.
804807
*/
805808
static GlobalTransaction
806-
TwoPhaseGetGXact(TransactionId xid)
809+
TwoPhaseGetGXact(TransactionId xid, bool lock_held)
807810
{
808811
GlobalTransaction result = NULL;
809812
int i;
810813

811814
static TransactionId cached_xid = InvalidTransactionId;
812815
static GlobalTransaction cached_gxact = NULL;
813816

817+
Assert(!lock_held || LWLockHeldByMe(TwoPhaseStateLock));
818+
814819
/*
815820
* During a recovery, COMMIT PREPARED, or ABORT PREPARED, we'll be called
816821
* repeatedly for the same XID. We can save work with a simple cache.
817822
*/
818823
if (xid == cached_xid)
819824
return cached_gxact;
820825

821-
LWLockAcquire(TwoPhaseStateLock, LW_SHARED);
826+
if (!lock_held)
827+
LWLockAcquire(TwoPhaseStateLock, LW_SHARED);
822828

823829
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
824830
{
@@ -832,7 +838,8 @@ TwoPhaseGetGXact(TransactionId xid)
832838
}
833839
}
834840

835-
LWLockRelease(TwoPhaseStateLock);
841+
if (!lock_held)
842+
LWLockRelease(TwoPhaseStateLock);
836843

837844
if (result == NULL) /* should not happen */
838845
elog(ERROR, "failed to find GlobalTransaction for xid %u", xid);
@@ -849,24 +856,28 @@ TwoPhaseGetGXact(TransactionId xid)
849856
*
850857
* Dummy backend IDs are similar to real backend IDs of real backends.
851858
* They start at MaxBackends + 1, and are unique across all currently active
852-
* real backends and prepared transactions.
859+
* real backends and prepared transactions. If lock_held is set to true,
860+
* TwoPhaseStateLock will not be taken, so the caller had better hold it.
853861
*/
854862
BackendId
855-
TwoPhaseGetDummyBackendId(TransactionId xid)
863+
TwoPhaseGetDummyBackendId(TransactionId xid, bool lock_held)
856864
{
857-
GlobalTransaction gxact = TwoPhaseGetGXact(xid);
865+
GlobalTransaction gxact = TwoPhaseGetGXact(xid, lock_held);
858866

859867
return gxact->dummyBackendId;
860868
}
861869

862870
/*
863871
* TwoPhaseGetDummyProc
864872
* Get the PGPROC that represents a prepared transaction specified by XID
873+
*
874+
* If lock_held is set to true, TwoPhaseStateLock will not be taken, so the
875+
* caller had better hold it.
865876
*/
866877
PGPROC *
867-
TwoPhaseGetDummyProc(TransactionId xid)
878+
TwoPhaseGetDummyProc(TransactionId xid, bool lock_held)
868879
{
869-
GlobalTransaction gxact = TwoPhaseGetGXact(xid);
880+
GlobalTransaction gxact = TwoPhaseGetGXact(xid, lock_held);
870881

871882
return &ProcGlobal->allProcs[gxact->pgprocno];
872883
}
@@ -1560,6 +1571,14 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
15601571
if (hdr->initfileinval)
15611572
RelationCacheInitFilePostInvalidate();
15621573

1574+
/*
1575+
* Acquire the two-phase lock. We want to work on the two-phase callbacks
1576+
* while holding it to avoid potential conflicts with other transactions
1577+
* attempting to use the same GID, so the lock is released once the shared
1578+
* memory state is cleared.
1579+
*/
1580+
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
1581+
15631582
/* And now do the callbacks */
15641583
if (isCommit)
15651584
ProcessRecords(bufptr, xid, twophase_postcommit_callbacks);
@@ -1568,6 +1587,15 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
15681587

15691588
PredicateLockTwoPhaseFinish(xid, isCommit);
15701589

1590+
/* Clear shared memory state */
1591+
RemoveGXact(gxact);
1592+
1593+
/*
1594+
* Release the lock as all callbacks are called and shared memory cleanup
1595+
* is done.
1596+
*/
1597+
LWLockRelease(TwoPhaseStateLock);
1598+
15711599
/* Count the prepared xact as committed or aborted */
15721600
AtEOXact_PgStat(isCommit);
15731601

@@ -1577,9 +1605,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
15771605
if (gxact->ondisk)
15781606
RemoveTwoPhaseFile(xid, true);
15791607

1580-
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
1581-
RemoveGXact(gxact);
1582-
LWLockRelease(TwoPhaseStateLock);
15831608
MyLockedGxact = NULL;
15841609

15851610
RESUME_INTERRUPTS();

src/backend/storage/lmgr/lock.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3243,7 +3243,7 @@ AtPrepare_Locks(void)
32433243
void
32443244
PostPrepare_Locks(TransactionId xid)
32453245
{
3246-
PGPROC *newproc = TwoPhaseGetDummyProc(xid);
3246+
PGPROC *newproc = TwoPhaseGetDummyProc(xid, false);
32473247
HASH_SEQ_STATUS status;
32483248
LOCALLOCK *locallock;
32493249
LOCK *lock;
@@ -4034,7 +4034,7 @@ lock_twophase_recover(TransactionId xid, uint16 info,
40344034
void *recdata, uint32 len)
40354035
{
40364036
TwoPhaseLockRecord *rec = (TwoPhaseLockRecord *) recdata;
4037-
PGPROC *proc = TwoPhaseGetDummyProc(xid);
4037+
PGPROC *proc = TwoPhaseGetDummyProc(xid, false);
40384038
LOCKTAG *locktag;
40394039
LOCKMODE lockmode;
40404040
LOCKMETHODID lockmethodid;
@@ -4247,7 +4247,7 @@ lock_twophase_postcommit(TransactionId xid, uint16 info,
42474247
void *recdata, uint32 len)
42484248
{
42494249
TwoPhaseLockRecord *rec = (TwoPhaseLockRecord *) recdata;
4250-
PGPROC *proc = TwoPhaseGetDummyProc(xid);
4250+
PGPROC *proc = TwoPhaseGetDummyProc(xid, true);
42514251
LOCKTAG *locktag;
42524252
LOCKMETHODID lockmethodid;
42534253
LockMethod lockMethodTable;

src/include/access/twophase.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ extern void TwoPhaseShmemInit(void);
3434
extern void AtAbort_Twophase(void);
3535
extern void PostPrepare_Twophase(void);
3636

37-
extern PGPROC *TwoPhaseGetDummyProc(TransactionId xid);
38-
extern BackendId TwoPhaseGetDummyBackendId(TransactionId xid);
37+
extern PGPROC *TwoPhaseGetDummyProc(TransactionId xid, bool lock_held);
38+
extern BackendId TwoPhaseGetDummyBackendId(TransactionId xid, bool lock_held);
3939

4040
extern GlobalTransaction MarkAsPreparing(TransactionId xid, const char *gid,
4141
TimestampTz prepared_at,

0 commit comments

Comments
 (0)