diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c new file mode 100644 index 581837a..b993ca8 *** a/src/backend/postmaster/startup.c --- b/src/backend/postmaster/startup.c *************** StartupProcessMain(void) *** 203,208 **** --- 203,209 ---- */ RegisterTimeout(STANDBY_DEADLOCK_TIMEOUT, StandbyDeadLockHandler); RegisterTimeout(STANDBY_TIMEOUT, StandbyTimeoutHandler); + RegisterTimeout(STANDBY_LOCK_TIMEOUT, StandbyLockTimeoutHandler); /* * Unblock signals (they were blocked when the postmaster forked us) diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c new file mode 100644 index 292bed5..4b428de *** a/src/backend/storage/ipc/standby.c --- b/src/backend/storage/ipc/standby.c *************** static List *RecoveryLockList; *** 41,47 **** static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist, ProcSignalReason reason); - static void ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid); static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason); static XLogRecPtr LogCurrentRunningXacts(RunningTransactions CurrRunningXacts); static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks); --- 41,46 ---- *************** ResolveRecoveryConflictWithDatabase(Oid *** 337,375 **** } } ! static void ! ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid) { ! VirtualTransactionId *backends; ! bool lock_acquired = false; ! int num_attempts = 0; ! LOCKTAG locktag; ! SET_LOCKTAG_RELATION(locktag, dbOid, relOid); ! /* ! * If blowing away everybody with conflicting locks doesn't work, after ! * the first two attempts then we just start blowing everybody away until ! * it does work. We do this because its likely that we either have too ! * many locks and we just can't get one at all, or that there are many ! * people crowding for the same table. Recovery must win; the end ! * justifies the means. ! */ ! while (!lock_acquired) ! { ! if (++num_attempts < 3) ! backends = GetLockConflicts(&locktag, AccessExclusiveLock); ! else ! backends = GetConflictingVirtualXIDs(InvalidTransactionId, ! InvalidOid); ResolveRecoveryConflictWithVirtualXIDs(backends, PROCSIG_RECOVERY_CONFLICT_LOCK); ! if (LockAcquireExtended(&locktag, AccessExclusiveLock, true, true, false) ! != LOCKACQUIRE_NOT_AVAIL) ! lock_acquired = true; } } /* --- 336,402 ---- } } ! /* ! * ResolveRecoveryConflictWithLock is called from ProcSleep() ! * to resolve conflicts with other backends holding relation locks. ! * ! * The WaitLatch sleep normally done in ProcSleep() ! * (when not InHotStandby) is performed here, for code clarity. ! * ! * We either resolve conflicts immediately or set a timeout to wake us at ! * the limit of our patience. ! * ! * Resolve conflicts by cancelling to all backends holding a conflicting ! * lock. As we are already queued to be granted the lock, no new lock ! * requests conflicting with ours will be granted in the meantime. ! * ! * Deadlocks involving the Startup process and an ordinary backend process ! * will be detected by the deadlock detector within the ordinary backend. ! */ ! void ! ResolveRecoveryConflictWithLock(LOCKTAG locktag) { ! TimestampTz ltime; ! Assert(InHotStandby); ! ltime = GetStandbyLimitTime(); + if (GetCurrentTimestamp() >= ltime) + { + /* + * We're already behind, so clear a path as quickly as possible. + */ + VirtualTransactionId *backends; + backends = GetLockConflicts(&locktag, AccessExclusiveLock); ResolveRecoveryConflictWithVirtualXIDs(backends, PROCSIG_RECOVERY_CONFLICT_LOCK); + } + else + { + /* + * Wake up at ltime + */ + EnableTimeoutParams timeouts[1]; ! timeouts[0].id = STANDBY_LOCK_TIMEOUT; ! timeouts[0].type = TMPARAM_AT; ! timeouts[0].fin_time = ltime; ! enable_timeouts(timeouts, 1); } + + /* Wait to be signaled by the release of the Relation Lock */ + ProcWaitForSignal(); + + /* + * Clear any timeout requests established above. We assume here that the + * Startup process doesn't have any other outstanding timeouts than what + * this function + * uses. If that stops being true, we could cancel the timeouts + * individually, but that'd be slower. + */ + disable_all_timeouts(false); + } /* *************** StandbyTimeoutHandler(void) *** 532,537 **** --- 559,574 ---- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); } + /* + * StandbyLockTimeoutHandler() will be called if STANDBY_LOCK_TIMEOUT is exceeded. + * This doesn't need to do anything, simply waking up is enough. + */ + void + StandbyLockTimeoutHandler(void) + { + /* forget any pending STANDBY_DEADLOCK_TIMEOUT request */ + disable_timeout(STANDBY_DEADLOCK_TIMEOUT, false); + } /* * ----------------------------------------------------- *************** StandbyTimeoutHandler(void) *** 545,551 **** * process is the proxy by which the original locks are implemented. * * We only keep track of AccessExclusiveLocks, which are only ever held by ! * one transaction on one relation, and don't worry about lock queuing. * * We keep a single dynamically expandible list of locks in local memory, * RelationLockList, so we can keep track of the various entries made by --- 582,588 ---- * process is the proxy by which the original locks are implemented. * * We only keep track of AccessExclusiveLocks, which are only ever held by ! * one transaction on one relation. * * We keep a single dynamically expandible list of locks in local memory, * RelationLockList, so we can keep track of the various entries made by *************** StandbyAcquireAccessExclusiveLock(Transa *** 587,600 **** newlock->relOid = relOid; RecoveryLockList = lappend(RecoveryLockList, newlock); - /* - * Attempt to acquire the lock as requested, if not resolve conflict - */ SET_LOCKTAG_RELATION(locktag, newlock->dbOid, newlock->relOid); ! if (LockAcquireExtended(&locktag, AccessExclusiveLock, true, true, false) ! == LOCKACQUIRE_NOT_AVAIL) ! ResolveRecoveryConflictWithLock(newlock->dbOid, newlock->relOid); } static void --- 624,632 ---- newlock->relOid = relOid; RecoveryLockList = lappend(RecoveryLockList, newlock); SET_LOCKTAG_RELATION(locktag, newlock->dbOid, newlock->relOid); ! LockAcquireExtended(&locktag, AccessExclusiveLock, true, false, false); } static void diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c new file mode 100644 index bb10c1b..4f13f9d *** a/src/backend/storage/lmgr/proc.c --- b/src/backend/storage/lmgr/proc.c *************** *** 42,47 **** --- 42,48 ---- #include "postmaster/autovacuum.h" #include "replication/slot.h" #include "replication/syncrep.h" + #include "storage/standby.h" #include "storage/ipc.h" #include "storage/lmgr.h" #include "storage/pmsignal.h" *************** ProcSleep(LOCALLOCK *locallock, LockMeth *** 1080,1099 **** * If LockTimeout is set, also enable the timeout for that. We can save a * few cycles by enabling both timeout sources in one call. */ ! if (LockTimeout > 0) { ! EnableTimeoutParams timeouts[2]; ! ! timeouts[0].id = DEADLOCK_TIMEOUT; ! timeouts[0].type = TMPARAM_AFTER; ! timeouts[0].delay_ms = DeadlockTimeout; ! timeouts[1].id = LOCK_TIMEOUT; ! timeouts[1].type = TMPARAM_AFTER; ! timeouts[1].delay_ms = LockTimeout; ! enable_timeouts(timeouts, 2); } - else - enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout); /* * If somebody wakes us between LWLockRelease and WaitLatch, the latch --- 1081,1103 ---- * If LockTimeout is set, also enable the timeout for that. We can save a * few cycles by enabling both timeout sources in one call. */ ! if (!InHotStandby) { ! if (LockTimeout > 0) ! { ! EnableTimeoutParams timeouts[2]; ! ! timeouts[0].id = DEADLOCK_TIMEOUT; ! timeouts[0].type = TMPARAM_AFTER; ! timeouts[0].delay_ms = DeadlockTimeout; ! timeouts[1].id = LOCK_TIMEOUT; ! timeouts[1].type = TMPARAM_AFTER; ! timeouts[1].delay_ms = LockTimeout; ! enable_timeouts(timeouts, 2); ! } ! else ! enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout); } /* * If somebody wakes us between LWLockRelease and WaitLatch, the latch *************** ProcSleep(LOCALLOCK *locallock, LockMeth *** 1111,1125 **** */ do { ! WaitLatch(MyLatch, WL_LATCH_SET, 0); ! ResetLatch(MyLatch); ! /* check for deadlocks first, as that's probably log-worthy */ ! if (got_deadlock_timeout) { ! CheckDeadLock(); ! got_deadlock_timeout = false; } - CHECK_FOR_INTERRUPTS(); /* * waitStatus could change from STATUS_WAITING to something else --- 1115,1137 ---- */ do { ! if (InHotStandby) { ! /* Set a timer and wait for that or for the Lock to be granted */ ! ResolveRecoveryConflictWithLock(locallock->tag.lock); ! } ! else ! { ! WaitLatch(MyLatch, WL_LATCH_SET, 0); ! ResetLatch(MyLatch); ! /* check for deadlocks first, as that's probably log-worthy */ ! if (got_deadlock_timeout) ! { ! CheckDeadLock(); ! got_deadlock_timeout = false; ! } ! CHECK_FOR_INTERRUPTS(); } /* * waitStatus could change from STATUS_WAITING to something else diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h new file mode 100644 index f27a7ba..957ba19 *** a/src/include/storage/standby.h --- b/src/include/storage/standby.h *************** *** 15,20 **** --- 15,21 ---- #define STANDBY_H #include "storage/standbydefs.h" + #include "storage/lock.h" #include "storage/procsignal.h" #include "storage/relfilenode.h" *************** extern void ResolveRecoveryConflictWithS *** 31,40 **** --- 32,43 ---- extern void ResolveRecoveryConflictWithTablespace(Oid tsid); extern void ResolveRecoveryConflictWithDatabase(Oid dbid); + extern void ResolveRecoveryConflictWithLock(LOCKTAG locktag); extern void ResolveRecoveryConflictWithBufferPin(void); extern void CheckRecoveryConflictDeadlock(void); extern void StandbyDeadLockHandler(void); extern void StandbyTimeoutHandler(void); + extern void StandbyLockTimeoutHandler(void); /* * Standby Rmgr (RM_STANDBY_ID) diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h new file mode 100644 index 30581a1..33b5016 *** a/src/include/utils/timeout.h --- b/src/include/utils/timeout.h *************** typedef enum TimeoutId *** 29,34 **** --- 29,35 ---- STATEMENT_TIMEOUT, STANDBY_DEADLOCK_TIMEOUT, STANDBY_TIMEOUT, + STANDBY_LOCK_TIMEOUT, /* First user-definable timeout reason */ USER_TIMEOUT, /* Maximum number of timeout reasons */