Skip to content

Commit 37c5486

Browse files
Rework wait for AccessExclusiveLocks on Hot Standby
Earlier version committed in 9.0 caused spurious waits in some cases. New infrastructure for lock waits in 9.3 used to correct and improve this. Jeff Janes based upon a proposal by Simon Riggs, who also reviewed Additional review comments from Amit Kapila
1 parent 53be0b1 commit 37c5486

File tree

5 files changed

+112
-61
lines changed

5 files changed

+112
-61
lines changed

src/backend/postmaster/startup.c

+1
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ StartupProcessMain(void)
203203
*/
204204
RegisterTimeout(STANDBY_DEADLOCK_TIMEOUT, StandbyDeadLockHandler);
205205
RegisterTimeout(STANDBY_TIMEOUT, StandbyTimeoutHandler);
206+
RegisterTimeout(STANDBY_LOCK_TIMEOUT, StandbyLockTimeoutHandler);
206207

207208
/*
208209
* Unblock signals (they were blocked when the postmaster forked us)

src/backend/storage/ipc/standby.c

+61-33
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ static List *RecoveryLockList;
4141

4242
static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
4343
ProcSignalReason reason);
44-
static void ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid);
4544
static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason);
4645
static XLogRecPtr LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
4746
static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks);
@@ -339,39 +338,65 @@ ResolveRecoveryConflictWithDatabase(Oid dbid)
339338
}
340339
}
341340

342-
static void
343-
ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid)
341+
/*
342+
* ResolveRecoveryConflictWithLock is called from ProcSleep()
343+
* to resolve conflicts with other backends holding relation locks.
344+
*
345+
* The WaitLatch sleep normally done in ProcSleep()
346+
* (when not InHotStandby) is performed here, for code clarity.
347+
*
348+
* We either resolve conflicts immediately or set a timeout to wake us at
349+
* the limit of our patience.
350+
*
351+
* Resolve conflicts by cancelling to all backends holding a conflicting
352+
* lock. As we are already queued to be granted the lock, no new lock
353+
* requests conflicting with ours will be granted in the meantime.
354+
*
355+
* Deadlocks involving the Startup process and an ordinary backend process
356+
* will be detected by the deadlock detector within the ordinary backend.
357+
*/
358+
void
359+
ResolveRecoveryConflictWithLock(LOCKTAG locktag)
344360
{
345-
VirtualTransactionId *backends;
346-
bool lock_acquired = false;
347-
int num_attempts = 0;
348-
LOCKTAG locktag;
361+
TimestampTz ltime;
349362

350-
SET_LOCKTAG_RELATION(locktag, dbOid, relOid);
363+
Assert(InHotStandby);
351364

352-
/*
353-
* If blowing away everybody with conflicting locks doesn't work, after
354-
* the first two attempts then we just start blowing everybody away until
355-
* it does work. We do this because its likely that we either have too
356-
* many locks and we just can't get one at all, or that there are many
357-
* people crowding for the same table. Recovery must win; the end
358-
* justifies the means.
359-
*/
360-
while (!lock_acquired)
361-
{
362-
if (++num_attempts < 3)
363-
backends = GetLockConflicts(&locktag, AccessExclusiveLock);
364-
else
365-
backends = GetConflictingVirtualXIDs(InvalidTransactionId,
366-
InvalidOid);
365+
ltime = GetStandbyLimitTime();
367366

367+
if (GetCurrentTimestamp() >= ltime)
368+
{
369+
/*
370+
* We're already behind, so clear a path as quickly as possible.
371+
*/
372+
VirtualTransactionId *backends;
373+
backends = GetLockConflicts(&locktag, AccessExclusiveLock);
368374
ResolveRecoveryConflictWithVirtualXIDs(backends,
369375
PROCSIG_RECOVERY_CONFLICT_LOCK);
376+
}
377+
else
378+
{
379+
/*
380+
* Wait (or wait again) until ltime
381+
*/
382+
EnableTimeoutParams timeouts[1];
370383

371-
if (LockAcquireExtended(&locktag, AccessExclusiveLock, true, true, false)
372-
!= LOCKACQUIRE_NOT_AVAIL)
373-
lock_acquired = true;
384+
timeouts[0].id = STANDBY_LOCK_TIMEOUT;
385+
timeouts[0].type = TMPARAM_AT;
386+
timeouts[0].fin_time = ltime;
387+
enable_timeouts(timeouts, 1);
374388
}
389+
390+
/* Wait to be signaled by the release of the Relation Lock */
391+
ProcWaitForSignal();
392+
393+
/*
394+
* Clear any timeout requests established above. We assume here that the
395+
* Startup process doesn't have any other outstanding timeouts than those
396+
* used by this function. If that stops being true, we could cancel the
397+
* timeouts individually, but that'd be slower.
398+
*/
399+
disable_all_timeouts(false);
375400
}
376401

377402
/*
@@ -534,6 +559,14 @@ StandbyTimeoutHandler(void)
534559
SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
535560
}
536561

562+
/*
563+
* StandbyLockTimeoutHandler() will be called if STANDBY_LOCK_TIMEOUT is exceeded.
564+
* This doesn't need to do anything, simply waking up is enough.
565+
*/
566+
void
567+
StandbyLockTimeoutHandler(void)
568+
{
569+
}
537570

538571
/*
539572
* -----------------------------------------------------
@@ -547,7 +580,7 @@ StandbyTimeoutHandler(void)
547580
* process is the proxy by which the original locks are implemented.
548581
*
549582
* We only keep track of AccessExclusiveLocks, which are only ever held by
550-
* one transaction on one relation, and don't worry about lock queuing.
583+
* one transaction on one relation.
551584
*
552585
* We keep a single dynamically expandible list of locks in local memory,
553586
* RelationLockList, so we can keep track of the various entries made by
@@ -589,14 +622,9 @@ StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid relOid)
589622
newlock->relOid = relOid;
590623
RecoveryLockList = lappend(RecoveryLockList, newlock);
591624

592-
/*
593-
* Attempt to acquire the lock as requested, if not resolve conflict
594-
*/
595625
SET_LOCKTAG_RELATION(locktag, newlock->dbOid, newlock->relOid);
596626

597-
if (LockAcquireExtended(&locktag, AccessExclusiveLock, true, true, false)
598-
== LOCKACQUIRE_NOT_AVAIL)
599-
ResolveRecoveryConflictWithLock(newlock->dbOid, newlock->relOid);
627+
LockAcquireExtended(&locktag, AccessExclusiveLock, true, false, false);
600628
}
601629

602630
static void

src/backend/storage/lmgr/proc.c

+46-28
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "postmaster/autovacuum.h"
4343
#include "replication/slot.h"
4444
#include "replication/syncrep.h"
45+
#include "storage/standby.h"
4546
#include "storage/ipc.h"
4647
#include "storage/lmgr.h"
4748
#include "storage/pmsignal.h"
@@ -1169,21 +1170,27 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
11691170
*
11701171
* If LockTimeout is set, also enable the timeout for that. We can save a
11711172
* few cycles by enabling both timeout sources in one call.
1173+
*
1174+
* If InHotStandby we set lock waits slightly later for clarity with other
1175+
* code.
11721176
*/
1173-
if (LockTimeout > 0)
1177+
if (!InHotStandby)
11741178
{
1175-
EnableTimeoutParams timeouts[2];
1176-
1177-
timeouts[0].id = DEADLOCK_TIMEOUT;
1178-
timeouts[0].type = TMPARAM_AFTER;
1179-
timeouts[0].delay_ms = DeadlockTimeout;
1180-
timeouts[1].id = LOCK_TIMEOUT;
1181-
timeouts[1].type = TMPARAM_AFTER;
1182-
timeouts[1].delay_ms = LockTimeout;
1183-
enable_timeouts(timeouts, 2);
1179+
if (LockTimeout > 0)
1180+
{
1181+
EnableTimeoutParams timeouts[2];
1182+
1183+
timeouts[0].id = DEADLOCK_TIMEOUT;
1184+
timeouts[0].type = TMPARAM_AFTER;
1185+
timeouts[0].delay_ms = DeadlockTimeout;
1186+
timeouts[1].id = LOCK_TIMEOUT;
1187+
timeouts[1].type = TMPARAM_AFTER;
1188+
timeouts[1].delay_ms = LockTimeout;
1189+
enable_timeouts(timeouts, 2);
1190+
}
1191+
else
1192+
enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);
11841193
}
1185-
else
1186-
enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);
11871194

11881195
/*
11891196
* If somebody wakes us between LWLockRelease and WaitLatch, the latch
@@ -1201,15 +1208,23 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
12011208
*/
12021209
do
12031210
{
1204-
WaitLatch(MyLatch, WL_LATCH_SET, 0);
1205-
ResetLatch(MyLatch);
1206-
/* check for deadlocks first, as that's probably log-worthy */
1207-
if (got_deadlock_timeout)
1211+
if (InHotStandby)
1212+
{
1213+
/* Set a timer and wait for that or for the Lock to be granted */
1214+
ResolveRecoveryConflictWithLock(locallock->tag.lock);
1215+
}
1216+
else
12081217
{
1209-
CheckDeadLock();
1210-
got_deadlock_timeout = false;
1218+
WaitLatch(MyLatch, WL_LATCH_SET, 0);
1219+
ResetLatch(MyLatch);
1220+
/* check for deadlocks first, as that's probably log-worthy */
1221+
if (got_deadlock_timeout)
1222+
{
1223+
CheckDeadLock();
1224+
got_deadlock_timeout = false;
1225+
}
1226+
CHECK_FOR_INTERRUPTS();
12111227
}
1212-
CHECK_FOR_INTERRUPTS();
12131228

12141229
/*
12151230
* waitStatus could change from STATUS_WAITING to something else
@@ -1447,18 +1462,21 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
14471462
* already caused QueryCancelPending to become set, we want the cancel to
14481463
* be reported as a lock timeout, not a user cancel.
14491464
*/
1450-
if (LockTimeout > 0)
1465+
if (!InHotStandby)
14511466
{
1452-
DisableTimeoutParams timeouts[2];
1467+
if (LockTimeout > 0)
1468+
{
1469+
DisableTimeoutParams timeouts[2];
14531470

1454-
timeouts[0].id = DEADLOCK_TIMEOUT;
1455-
timeouts[0].keep_indicator = false;
1456-
timeouts[1].id = LOCK_TIMEOUT;
1457-
timeouts[1].keep_indicator = true;
1458-
disable_timeouts(timeouts, 2);
1471+
timeouts[0].id = DEADLOCK_TIMEOUT;
1472+
timeouts[0].keep_indicator = false;
1473+
timeouts[1].id = LOCK_TIMEOUT;
1474+
timeouts[1].keep_indicator = true;
1475+
disable_timeouts(timeouts, 2);
1476+
}
1477+
else
1478+
disable_timeout(DEADLOCK_TIMEOUT, false);
14591479
}
1460-
else
1461-
disable_timeout(DEADLOCK_TIMEOUT, false);
14621480

14631481
/*
14641482
* Re-acquire the lock table's partition lock. We have to do this to hold

src/include/storage/standby.h

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#define STANDBY_H
1616

1717
#include "storage/standbydefs.h"
18+
#include "storage/lock.h"
1819
#include "storage/procsignal.h"
1920
#include "storage/relfilenode.h"
2021

@@ -31,10 +32,12 @@ extern void ResolveRecoveryConflictWithSnapshot(TransactionId latestRemovedXid,
3132
extern void ResolveRecoveryConflictWithTablespace(Oid tsid);
3233
extern void ResolveRecoveryConflictWithDatabase(Oid dbid);
3334

35+
extern void ResolveRecoveryConflictWithLock(LOCKTAG locktag);
3436
extern void ResolveRecoveryConflictWithBufferPin(void);
3537
extern void CheckRecoveryConflictDeadlock(void);
3638
extern void StandbyDeadLockHandler(void);
3739
extern void StandbyTimeoutHandler(void);
40+
extern void StandbyLockTimeoutHandler(void);
3841

3942
/*
4043
* Standby Rmgr (RM_STANDBY_ID)

src/include/utils/timeout.h

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ typedef enum TimeoutId
2929
STATEMENT_TIMEOUT,
3030
STANDBY_DEADLOCK_TIMEOUT,
3131
STANDBY_TIMEOUT,
32+
STANDBY_LOCK_TIMEOUT,
3233
/* First user-definable timeout reason */
3334
USER_TIMEOUT,
3435
/* Maximum number of timeout reasons */

0 commit comments

Comments
 (0)