Commit ed78384a authored by Simon Riggs's avatar Simon Riggs

Move call to GetTopTransactionId() earlier in LockAcquire(),

removing an infrequently occurring race condition in Hot Standby.
An xid must be assigned before a lock appears in shared memory,
rather than immediately after, else GetRunningTransactionLocks()
may see InvalidTransactionId, causing assertion failures during
lock processing on standby.

Bug report and diagnosis by Fujii Masao, fix by me.
parent c623365f
...@@ -953,14 +953,6 @@ LogAccessExclusiveLock(Oid dbOid, Oid relOid) ...@@ -953,14 +953,6 @@ LogAccessExclusiveLock(Oid dbOid, Oid relOid)
{ {
xl_standby_lock xlrec; xl_standby_lock xlrec;
/*
* Ensure that a TransactionId has been assigned to this transaction. We
* don't actually need the xid yet but if we don't do this then
* RecordTransactionCommit() and RecordTransactionAbort() will optimise
* away the transaction completion record which recovery relies upon to
* release locks. It's a hack, but for a corner case not worth adding code
* for into the main commit path.
*/
xlrec.xid = GetTopTransactionId(); xlrec.xid = GetTopTransactionId();
/* /*
...@@ -973,3 +965,24 @@ LogAccessExclusiveLock(Oid dbOid, Oid relOid) ...@@ -973,3 +965,24 @@ LogAccessExclusiveLock(Oid dbOid, Oid relOid)
LogAccessExclusiveLocks(1, &xlrec); LogAccessExclusiveLocks(1, &xlrec);
} }
/*
* Prepare to log an AccessExclusiveLock, for use during LockAcquire()
*/
void
LogAccessExclusiveLockPrepare(void)
{
/*
* Ensure that a TransactionId has been assigned to this transaction,
* for two reasons, both related to lock release on the standby.
* First, we must assign an xid so that RecordTransactionCommit() and
* RecordTransactionAbort() do not optimise away the transaction
* completion record which recovery relies upon to release locks. It's
* a hack, but for a corner case not worth adding code for into the
* main commit path. Second, must must assign an xid before the lock
* is recorded in shared memory, otherwise a concurrently executing
* GetRunningTransactionLocks() might see a lock associated with an
* InvalidTransactionId which we later assert cannot happen.
*/
(void) GetTopTransactionId();
}
...@@ -499,6 +499,7 @@ LockAcquireExtended(const LOCKTAG *locktag, ...@@ -499,6 +499,7 @@ LockAcquireExtended(const LOCKTAG *locktag,
int partition; int partition;
LWLockId partitionLock; LWLockId partitionLock;
int status; int status;
bool log_lock = false;
if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods))
elog(ERROR, "unrecognized lock method: %d", lockmethodid); elog(ERROR, "unrecognized lock method: %d", lockmethodid);
...@@ -579,6 +580,24 @@ LockAcquireExtended(const LOCKTAG *locktag, ...@@ -579,6 +580,24 @@ LockAcquireExtended(const LOCKTAG *locktag,
return LOCKACQUIRE_ALREADY_HELD; return LOCKACQUIRE_ALREADY_HELD;
} }
/*
* Emit a WAL record if acquisition of this lock needs to be replayed in a
* standby server. Only AccessExclusiveLocks can conflict with lock types
* that read-only transactions can acquire in a standby server.
*
* Make sure this definition matches the one in GetRunningTransactionLocks().
*
* First we prepare to log, then after lock acquired we issue log record.
*/
if (lockmode >= AccessExclusiveLock &&
locktag->locktag_type == LOCKTAG_RELATION &&
!RecoveryInProgress() &&
XLogStandbyInfoActive())
{
LogAccessExclusiveLockPrepare();
log_lock = true;
}
/* /*
* Otherwise we've got to mess with the shared lock table. * Otherwise we've got to mess with the shared lock table.
*/ */
...@@ -868,15 +887,9 @@ LockAcquireExtended(const LOCKTAG *locktag, ...@@ -868,15 +887,9 @@ LockAcquireExtended(const LOCKTAG *locktag,
/* /*
* Emit a WAL record if acquisition of this lock need to be replayed in a * Emit a WAL record if acquisition of this lock need to be replayed in a
* standby server. Only AccessExclusiveLocks can conflict with lock types * standby server.
* that read-only transactions can acquire in a standby server.
*
* Make sure this definition matches the one GetRunningTransactionLocks().
*/ */
if (lockmode >= AccessExclusiveLock && if (log_lock)
locktag->locktag_type == LOCKTAG_RELATION &&
!RecoveryInProgress() &&
XLogStandbyInfoActive())
{ {
/* /*
* Decode the locktag back to the original values, to avoid sending * Decode the locktag back to the original values, to avoid sending
......
...@@ -109,6 +109,7 @@ typedef struct RunningTransactionsData ...@@ -109,6 +109,7 @@ typedef struct RunningTransactionsData
typedef RunningTransactionsData *RunningTransactions; typedef RunningTransactionsData *RunningTransactions;
extern void LogAccessExclusiveLock(Oid dbOid, Oid relOid); extern void LogAccessExclusiveLock(Oid dbOid, Oid relOid);
extern void LogAccessExclusiveLockPrepare(void);
extern void LogStandbySnapshot(TransactionId *oldestActiveXid, TransactionId *nextXid); extern void LogStandbySnapshot(TransactionId *oldestActiveXid, TransactionId *nextXid);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment