Skip to content

Commit e90ceea

Browse files
committed
Avoid bogus TwoPhaseState locking sequences
The optimized code in 728bd99 contains a few invalid locking sequences. To wit, the original code would try to acquire an lwlock that it already holds. Avoid this by moving lock acquisitions to higher-level code, and install appropriate assertions in low-level that the correct mode is held. Authors: Michael Paquier, Álvaro Herrera Reported-By: chuanting wang Bug: #14680 Discussion: https://postgr.es/m/20170531033228.1487.10124@wrigleys.postgresql.org
1 parent 0d9bdbc commit e90ceea

File tree

2 files changed

+83
-72
lines changed

2 files changed

+83
-72
lines changed

src/backend/access/transam/twophase.c

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,10 @@ typedef struct TwoPhaseStateData
195195
static TwoPhaseStateData *TwoPhaseState;
196196

197197
/*
198-
* Global transaction entry currently locked by us, if any.
198+
* Global transaction entry currently locked by us, if any. Note that any
199+
* access to the entry pointed to by this variable must be protected by
200+
* TwoPhaseStateLock, though obviously the pointer itself doesn't need to be
201+
* (since it's just local memory).
199202
*/
200203
static GlobalTransaction MyLockedGxact = NULL;
201204

@@ -338,18 +341,13 @@ AtAbort_Twophase(void)
338341
* resources held by the transaction yet. In those cases, the in-memory
339342
* state can be wrong, but it's too late to back out.
340343
*/
344+
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
341345
if (!MyLockedGxact->valid)
342-
{
343346
RemoveGXact(MyLockedGxact);
344-
}
345347
else
346-
{
347-
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
348-
349348
MyLockedGxact->locking_backend = InvalidBackendId;
349+
LWLockRelease(TwoPhaseStateLock);
350350

351-
LWLockRelease(TwoPhaseStateLock);
352-
}
353351
MyLockedGxact = NULL;
354352
}
355353

@@ -454,6 +452,8 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid,
454452
PGXACT *pgxact;
455453
int i;
456454

455+
Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
456+
457457
Assert(gxact != NULL);
458458
proc = &ProcGlobal->allProcs[gxact->pgprocno];
459459
pgxact = &ProcGlobal->allPgXact[gxact->pgprocno];
@@ -530,15 +530,19 @@ GXactLoadSubxactData(GlobalTransaction gxact, int nsubxacts,
530530
/*
531531
* MarkAsPrepared
532532
* Mark the GXACT as fully valid, and enter it into the global ProcArray.
533+
*
534+
* lock_held indicates whether caller already holds TwoPhaseStateLock.
533535
*/
534536
static void
535-
MarkAsPrepared(GlobalTransaction gxact)
537+
MarkAsPrepared(GlobalTransaction gxact, bool lock_held)
536538
{
537539
/* Lock here may be overkill, but I'm not convinced of that ... */
538-
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
540+
if (!lock_held)
541+
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
539542
Assert(!gxact->valid);
540543
gxact->valid = true;
541-
LWLockRelease(TwoPhaseStateLock);
544+
if (!lock_held)
545+
LWLockRelease(TwoPhaseStateLock);
542546

543547
/*
544548
* Put it into the global ProcArray so TransactionIdIsInProgress considers
@@ -632,7 +636,7 @@ RemoveGXact(GlobalTransaction gxact)
632636
{
633637
int i;
634638

635-
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
639+
Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
636640

637641
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
638642
{
@@ -646,14 +650,10 @@ RemoveGXact(GlobalTransaction gxact)
646650
gxact->next = TwoPhaseState->freeGXacts;
647651
TwoPhaseState->freeGXacts = gxact;
648652

649-
LWLockRelease(TwoPhaseStateLock);
650-
651653
return;
652654
}
653655
}
654656

655-
LWLockRelease(TwoPhaseStateLock);
656-
657657
elog(ERROR, "failed to find %p in GlobalTransaction array", gxact);
658658
}
659659

@@ -1127,7 +1127,7 @@ EndPrepare(GlobalTransaction gxact)
11271127
* the xact crashed. Instead we have a window where the same XID appears
11281128
* twice in ProcArray, which is OK.
11291129
*/
1130-
MarkAsPrepared(gxact);
1130+
MarkAsPrepared(gxact, false);
11311131

11321132
/*
11331133
* Now we can mark ourselves as out of the commit critical section: a
@@ -1506,7 +1506,9 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
15061506
if (gxact->ondisk)
15071507
RemoveTwoPhaseFile(xid, true);
15081508

1509+
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
15091510
RemoveGXact(gxact);
1511+
LWLockRelease(TwoPhaseStateLock);
15101512
MyLockedGxact = NULL;
15111513

15121514
pfree(buf);
@@ -1734,6 +1736,7 @@ restoreTwoPhaseData(void)
17341736
struct dirent *clde;
17351737

17361738
cldir = AllocateDir(TWOPHASE_DIR);
1739+
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
17371740
while ((clde = ReadDir(cldir, TWOPHASE_DIR)) != NULL)
17381741
{
17391742
if (strlen(clde->d_name) == 8 &&
@@ -1752,6 +1755,7 @@ restoreTwoPhaseData(void)
17521755
PrepareRedoAdd(buf, InvalidXLogRecPtr, InvalidXLogRecPtr);
17531756
}
17541757
}
1758+
LWLockRelease(TwoPhaseStateLock);
17551759
FreeDir(cldir);
17561760
}
17571761

@@ -1792,7 +1796,7 @@ PrescanPreparedTransactions(TransactionId **xids_p, int *nxids_p)
17921796
int allocsize = 0;
17931797
int i;
17941798

1795-
LWLockAcquire(TwoPhaseStateLock, LW_SHARED);
1799+
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
17961800
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
17971801
{
17981802
TransactionId xid;
@@ -1867,7 +1871,7 @@ StandbyRecoverPreparedTransactions(void)
18671871
{
18681872
int i;
18691873

1870-
LWLockAcquire(TwoPhaseStateLock, LW_SHARED);
1874+
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
18711875
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
18721876
{
18731877
TransactionId xid;
@@ -1893,7 +1897,8 @@ StandbyRecoverPreparedTransactions(void)
18931897
* Scan the shared memory entries of TwoPhaseState and reload the state for
18941898
* each prepared transaction (reacquire locks, etc).
18951899
*
1896-
* This is run during database startup.
1900+
* This is run at the end of recovery, but before we allow backends to write
1901+
* WAL.
18971902
*
18981903
* At the end of recovery the way we take snapshots will change. We now need
18991904
* to mark all running transactions with their full SubTransSetParent() info
@@ -1907,9 +1912,7 @@ RecoverPreparedTransactions(void)
19071912
{
19081913
int i;
19091914

1910-
/*
1911-
* Don't need a lock in the recovery phase.
1912-
*/
1915+
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
19131916
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
19141917
{
19151918
TransactionId xid;
@@ -1955,21 +1958,20 @@ RecoverPreparedTransactions(void)
19551958
* Recreate its GXACT and dummy PGPROC. But, check whether it was
19561959
* added in redo and already has a shmem entry for it.
19571960
*/
1958-
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
19591961
MarkAsPreparingGuts(gxact, xid, gid,
19601962
hdr->prepared_at,
19611963
hdr->owner, hdr->database);
19621964

19631965
/* recovered, so reset the flag for entries generated by redo */
19641966
gxact->inredo = false;
19651967

1966-
LWLockRelease(TwoPhaseStateLock);
1967-
19681968
GXactLoadSubxactData(gxact, hdr->nsubxacts, subxids);
1969-
MarkAsPrepared(gxact);
1969+
MarkAsPrepared(gxact, true);
1970+
1971+
LWLockRelease(TwoPhaseStateLock);
19701972

19711973
/*
1972-
* Recover other state (notably locks) using resource managers
1974+
* Recover other state (notably locks) using resource managers.
19731975
*/
19741976
ProcessRecords(bufptr, xid, twophase_recover_callbacks);
19751977

@@ -1988,7 +1990,11 @@ RecoverPreparedTransactions(void)
19881990
PostPrepare_Twophase();
19891991

19901992
pfree(buf);
1993+
1994+
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
19911995
}
1996+
1997+
LWLockRelease(TwoPhaseStateLock);
19921998
}
19931999

19942000
/*
@@ -2014,6 +2020,8 @@ ProcessTwoPhaseBuffer(TransactionId xid,
20142020
TwoPhaseFileHeader *hdr;
20152021
int i;
20162022

2023+
Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
2024+
20172025
if (!fromdisk)
20182026
Assert(prepare_start_lsn != InvalidXLogRecPtr);
20192027

@@ -2030,8 +2038,8 @@ ProcessTwoPhaseBuffer(TransactionId xid,
20302038
else
20312039
{
20322040
ereport(WARNING,
2033-
(errmsg("removing stale two-phase state from"
2034-
" shared memory for \"%u\"", xid)));
2041+
(errmsg("removing stale two-phase state from shared memory for \"%u\"",
2042+
xid)));
20352043
PrepareRedoRemove(xid, true);
20362044
}
20372045
return NULL;
@@ -2308,6 +2316,7 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn, XLogRecPtr end_lsn)
23082316
const char *gid;
23092317
GlobalTransaction gxact;
23102318

2319+
Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
23112320
Assert(RecoveryInProgress());
23122321

23132322
bufptr = buf + MAXALIGN(sizeof(TwoPhaseFileHeader));
@@ -2324,7 +2333,6 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn, XLogRecPtr end_lsn)
23242333
* that it got added in the redo phase
23252334
*/
23262335

2327-
LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
23282336
/* Get a free gxact from the freelist */
23292337
if (TwoPhaseState->freeGXacts == NULL)
23302338
ereport(ERROR,
@@ -2350,17 +2358,17 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn, XLogRecPtr end_lsn)
23502358
Assert(TwoPhaseState->numPrepXacts < max_prepared_xacts);
23512359
TwoPhaseState->prepXacts[TwoPhaseState->numPrepXacts++] = gxact;
23522360

2353-
LWLockRelease(TwoPhaseStateLock);
2354-
2355-
elog(DEBUG2, "Adding 2PC data to shared memory %u", gxact->xid);
2361+
elog(DEBUG2, "added 2PC data in shared memory for transaction %u", gxact->xid);
23562362
}
23572363

23582364
/*
23592365
* PrepareRedoRemove
23602366
*
2361-
* Remove the corresponding gxact entry from TwoPhaseState. Also
2362-
* remove the 2PC file if a prepared transaction was saved via
2363-
* an earlier checkpoint.
2367+
* Remove the corresponding gxact entry from TwoPhaseState. Also remove
2368+
* the 2PC file if a prepared transaction was saved via an earlier checkpoint.
2369+
*
2370+
* Caller must hold TwoPhaseStateLock in exclusive mode, because TwoPhaseState
2371+
* is updated.
23642372
*/
23652373
void
23662374
PrepareRedoRemove(TransactionId xid, bool giveWarning)
@@ -2369,9 +2377,9 @@ PrepareRedoRemove(TransactionId xid, bool giveWarning)
23692377
int i;
23702378
bool found = false;
23712379

2380+
Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
23722381
Assert(RecoveryInProgress());
23732382

2374-
LWLockAcquire(TwoPhaseStateLock, LW_SHARED);
23752383
for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
23762384
{
23772385
gxact = TwoPhaseState->prepXacts[i];
@@ -2383,7 +2391,6 @@ PrepareRedoRemove(TransactionId xid, bool giveWarning)
23832391
break;
23842392
}
23852393
}
2386-
LWLockRelease(TwoPhaseStateLock);
23872394

23882395
/*
23892396
* Just leave if there is nothing, this is expected during WAL replay.
@@ -2394,7 +2401,7 @@ PrepareRedoRemove(TransactionId xid, bool giveWarning)
23942401
/*
23952402
* And now we can clean up any files we may have left.
23962403
*/
2397-
elog(DEBUG2, "Removing 2PC data from shared memory %u", xid);
2404+
elog(DEBUG2, "removing 2PC data for transaction %u", xid);
23982405
if (gxact->ondisk)
23992406
RemoveTwoPhaseFile(xid, giveWarning);
24002407
RemoveGXact(gxact);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy