Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c172b7b

Browse files
Resolve timing issue with logging locks for Hot Standby.
We log AccessExclusiveLocks for replay onto standby nodes, but because of timing issues on ProcArray it is possible to log a lock that is still held by a just committed transaction that is very soon to be removed. To avoid any timing issue we avoid applying locks made by transactions with InvalidXid. Simon Riggs, bug report Tom Lane, diagnosis Pavan Deolasee
1 parent b8a91d9 commit c172b7b

File tree

4 files changed

+88
-44
lines changed

4 files changed

+88
-44
lines changed

src/backend/storage/ipc/procarray.c

+1-7
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
499499
* Remove stale transactions, if any.
500500
*/
501501
ExpireOldKnownAssignedTransactionIds(running->oldestRunningXid);
502-
StandbyReleaseOldLocks(running->oldestRunningXid);
502+
StandbyReleaseOldLocks(running->xcnt, running->xids);
503503

504504
/*
505505
* If our snapshot is already valid, nothing else to do...
@@ -553,12 +553,6 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
553553
* OK, we need to initialise from the RunningTransactionsData record
554554
*/
555555

556-
/*
557-
* Release any locks belonging to old transactions that are not running
558-
* according to the running-xacts record.
559-
*/
560-
StandbyReleaseOldLocks(running->nextXid);
561-
562556
/*
563557
* Nobody else is running yet, but take locks anyhow
564558
*/

src/backend/storage/ipc/standby.c

+75-35
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,9 @@ StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid relOid)
525525
LOCKTAG locktag;
526526

527527
/* Already processed? */
528-
if (TransactionIdDidCommit(xid) || TransactionIdDidAbort(xid))
528+
if (!TransactionIdIsValid(xid) ||
529+
TransactionIdDidCommit(xid) ||
530+
TransactionIdDidAbort(xid))
529531
return;
530532

531533
elog(trace_recovery(DEBUG4),
@@ -607,34 +609,86 @@ StandbyReleaseLockTree(TransactionId xid, int nsubxids, TransactionId *subxids)
607609
}
608610

609611
/*
610-
* StandbyReleaseLocksMany
611-
* Release standby locks held by XIDs < removeXid
612-
*
613-
* If keepPreparedXacts is true, keep prepared transactions even if
614-
* they're older than removeXid
612+
* Called at end of recovery and when we see a shutdown checkpoint.
615613
*/
616-
static void
617-
StandbyReleaseLocksMany(TransactionId removeXid, bool keepPreparedXacts)
614+
void
615+
StandbyReleaseAllLocks(void)
616+
{
617+
ListCell *cell,
618+
*prev,
619+
*next;
620+
LOCKTAG locktag;
621+
622+
elog(trace_recovery(DEBUG2), "release all standby locks");
623+
624+
prev = NULL;
625+
for (cell = list_head(RecoveryLockList); cell; cell = next)
626+
{
627+
xl_standby_lock *lock = (xl_standby_lock *) lfirst(cell);
628+
629+
next = lnext(cell);
630+
631+
elog(trace_recovery(DEBUG4),
632+
"releasing recovery lock: xid %u db %u rel %u",
633+
lock->xid, lock->dbOid, lock->relOid);
634+
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
635+
if (!LockRelease(&locktag, AccessExclusiveLock, true))
636+
elog(LOG,
637+
"RecoveryLockList contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
638+
lock->xid, lock->dbOid, lock->relOid);
639+
RecoveryLockList = list_delete_cell(RecoveryLockList, cell, prev);
640+
pfree(lock);
641+
}
642+
}
643+
644+
/*
645+
* StandbyReleaseOldLocks
646+
* Release standby locks held by XIDs that aren't running, as long
647+
* as they're not prepared transactions.
648+
*/
649+
void
650+
StandbyReleaseOldLocks(int nxids, TransactionId *xids)
618651
{
619652
ListCell *cell,
620653
*prev,
621654
*next;
622655
LOCKTAG locktag;
623656

624-
/*
625-
* Release all matching locks.
626-
*/
627657
prev = NULL;
628658
for (cell = list_head(RecoveryLockList); cell; cell = next)
629659
{
630660
xl_standby_lock *lock = (xl_standby_lock *) lfirst(cell);
661+
bool remove = false;
631662

632663
next = lnext(cell);
633664

634-
if (!TransactionIdIsValid(removeXid) || TransactionIdPrecedes(lock->xid, removeXid))
665+
Assert(TransactionIdIsValid(lock->xid));
666+
667+
if (StandbyTransactionIdIsPrepared(lock->xid))
668+
remove = false;
669+
else
670+
{
671+
int i;
672+
bool found = false;
673+
674+
for (i = 0; i < nxids; i++)
675+
{
676+
if (lock->xid == xids[i])
677+
{
678+
found = true;
679+
break;
680+
}
681+
}
682+
683+
/*
684+
* If its not a running transaction, remove it.
685+
*/
686+
if (!found)
687+
remove = true;
688+
}
689+
690+
if (remove)
635691
{
636-
if (keepPreparedXacts && StandbyTransactionIdIsPrepared(lock->xid))
637-
continue;
638692
elog(trace_recovery(DEBUG4),
639693
"releasing recovery lock: xid %u db %u rel %u",
640694
lock->xid, lock->dbOid, lock->relOid);
@@ -651,27 +705,6 @@ StandbyReleaseLocksMany(TransactionId removeXid, bool keepPreparedXacts)
651705
}
652706
}
653707

654-
/*
655-
* Called at end of recovery and when we see a shutdown checkpoint.
656-
*/
657-
void
658-
StandbyReleaseAllLocks(void)
659-
{
660-
elog(trace_recovery(DEBUG2), "release all standby locks");
661-
StandbyReleaseLocksMany(InvalidTransactionId, false);
662-
}
663-
664-
/*
665-
* StandbyReleaseOldLocks
666-
* Release standby locks held by XIDs < removeXid, as long
667-
* as they're not prepared transactions.
668-
*/
669-
void
670-
StandbyReleaseOldLocks(TransactionId removeXid)
671-
{
672-
StandbyReleaseLocksMany(removeXid, true);
673-
}
674-
675708
/*
676709
* --------------------------------------------------------------------
677710
* Recovery handling for Rmgr RM_STANDBY_ID
@@ -813,6 +846,13 @@ standby_desc(StringInfo buf, uint8 xl_info, char *rec)
813846
* Later, when we apply the running xact data we must be careful to ignore
814847
* transactions already committed, since those commits raced ahead when
815848
* making WAL entries.
849+
*
850+
* The loose timing also means that locks may be recorded that have a
851+
* zero xid, since xids are removed from procs before locks are removed.
852+
* So we must prune the lock list down to ensure we hold locks only for
853+
* currently running xids, performed by StandbyReleaseOldLocks().
854+
* Zero xids should no longer be possible, but we may be replaying WAL
855+
* from a time when they were possible.
816856
*/
817857
void
818858
LogStandbySnapshot(TransactionId *nextXid)

src/backend/storage/lmgr/lock.c

+11-1
Original file line numberDiff line numberDiff line change
@@ -3190,8 +3190,18 @@ GetRunningTransactionLocks(int *nlocks)
31903190
PGPROC *proc = proclock->tag.myProc;
31913191
PGXACT *pgxact = &ProcGlobal->allPgXact[proc->pgprocno];
31923192
LOCK *lock = proclock->tag.myLock;
3193+
TransactionId xid = pgxact->xid;
31933194

3194-
accessExclusiveLocks[index].xid = pgxact->xid;
3195+
/*
3196+
* Don't record locks for transactions if we know they have already
3197+
* issued their WAL record for commit but not yet released lock.
3198+
* It is still possible that we see locks held by already complete
3199+
* transactions, if they haven't yet zeroed their xids.
3200+
*/
3201+
if (!TransactionIdIsValid(xid))
3202+
continue;
3203+
3204+
accessExclusiveLocks[index].xid = xid;
31953205
accessExclusiveLocks[index].dbOid = lock->tag.locktag_field1;
31963206
accessExclusiveLocks[index].relOid = lock->tag.locktag_field2;
31973207

src/include/storage/standby.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ extern void StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid
4848
extern void StandbyReleaseLockTree(TransactionId xid,
4949
int nsubxids, TransactionId *subxids);
5050
extern void StandbyReleaseAllLocks(void);
51-
extern void StandbyReleaseOldLocks(TransactionId removeXid);
51+
extern void StandbyReleaseOldLocks(int nxids, TransactionId *xids);
5252

5353
/*
5454
* XLOG message types

0 commit comments

Comments
 (0)