/* * ResolveRecoveryConflictWithBufferPin is called from LockBufferForCleanup() * to resolve conflicts with other backends holding buffer pins. * * We either resolve conflicts immediately or set a SIGALRM to wake us at * the limit of our patience. The sleep in LockBufferForCleanup() is * performed here, for code clarity. * * Resolve conflicts by sending a PROCSIG signal to all backends to check if * they hold one of the buffer pins that is blocking Startup process. If so, * backends will take an appropriate error action, ERROR or FATAL. * * We also must check for deadlocks. Deadlocks occur because if queries * wait on a lock, that must be behind an AccessExclusiveLock, which can only * be cleared if the Startup process replays a transaction completion record. * If Startup process is also waiting then that is a deadlock. The deadlock * can occur if the query is waiting and then the Startup sleeps, or if * Startup is sleeping and the query waits on a lock. We protect against * only the former sequence here, the latter sequence is checked prior to * the query sleeping, in CheckRecoveryConflictDeadlock(). * * Deadlocks are extremely rare, and relatively expensive to check for, * so we don't do a deadlock check right away ... only if we have had to wait * at least deadlock_timeout. Most of the logic about that is in proc.c. */ void ResolveRecoveryConflictWithBufferPin(void) { bool sig_alarm_enabled = false; TimestampTz ltime; TimestampTz now; Assert(InHotStandby); ltime = GetStandbyLimitTime(); now = GetCurrentTimestamp(); if (!ltime) { /* * We're willing to wait forever for conflicts, so set timeout for * deadlock check (only) */ if (enable_standby_sig_alarm(now, now, true)) sig_alarm_enabled = true; else elog(FATAL, "could not set timer for process wakeup"); } else if (now >= ltime) { /* * We're already behind, so clear a path as quickly as possible. */ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); } else { /* * Wake up at ltime, and check for deadlocks as well if we will be * waiting longer than deadlock_timeout */ if (enable_standby_sig_alarm(now, ltime, false)) sig_alarm_enabled = true; else elog(FATAL, "could not set timer for process wakeup"); } /* Wait to be signaled by UnpinBuffer() */ ProcWaitForSignal(); if (sig_alarm_enabled) { if (!disable_standby_sig_alarm()) elog(FATAL, "could not disable timer for process wakeup"); } }
/* * ResolveRecoveryConflictWithBufferPin is called from LockBufferForCleanup() * to resolve conflicts with other backends holding buffer pins. * * We either resolve conflicts immediately or set a SIGALRM to wake us at * the limit of our patience. The sleep in LockBufferForCleanup() is * performed here, for code clarity. * * Resolve conflict by sending a SIGUSR1 reason to all backends to check if * they hold one of the buffer pins that is blocking Startup process. If so, * backends will take an appropriate error action, ERROR or FATAL. * * We also check for deadlocks before we wait, though applications that cause * these will be extremely rare. Deadlocks occur because if queries * wait on a lock, that must be behind an AccessExclusiveLock, which can only * be cleared if the Startup process replays a transaction completion record. * If Startup process is also waiting then that is a deadlock. The deadlock * can occur if the query is waiting and then the Startup sleeps, or if * Startup is sleeping and the query waits on a lock. We protect against * only the former sequence here, the latter sequence is checked prior to * the query sleeping, in CheckRecoveryConflictDeadlock(). */ void ResolveRecoveryConflictWithBufferPin(void) { bool sig_alarm_enabled = false; Assert(InHotStandby); if (MaxStandbyDelay == 0) { /* * We don't want to wait, so just tell everybody holding the pin to * get out of town. */ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); } else if (MaxStandbyDelay < 0) { /* * Send out a request to check for buffer pin deadlocks before we * wait. This is fairly cheap, so no need to wait for deadlock timeout * before trying to send it out. */ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK); } else { TimestampTz then = GetLatestXLogTime(); TimestampTz now = GetCurrentTimestamp(); /* Are we past max_standby_delay? */ if (TimestampDifferenceExceeds(then, now, MaxStandbyDelay)) { /* * We're already behind, so clear a path as quickly as possible. */ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); } else { TimestampTz fin_time; /* Expected wake-up time by timer */ long timer_delay_secs; /* Amount of time we set timer * for */ int timer_delay_usecs; /* * Send out a request to check for buffer pin deadlocks before we * wait. This is fairly cheap, so no need to wait for deadlock * timeout before trying to send it out. */ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK); /* * How much longer we should wait? */ fin_time = TimestampTzPlusMilliseconds(then, MaxStandbyDelay); TimestampDifference(now, fin_time, &timer_delay_secs, &timer_delay_usecs); /* * It's possible that the difference is less than a microsecond; * ensure we don't cancel, rather than set, the interrupt. */ if (timer_delay_secs == 0 && timer_delay_usecs == 0) timer_delay_usecs = 1; if (enable_standby_sig_alarm(timer_delay_secs, timer_delay_usecs, fin_time)) sig_alarm_enabled = true; else elog(FATAL, "could not set timer for process wakeup"); } } /* Wait to be signaled by UnpinBuffer() */ ProcWaitForSignal(); if (sig_alarm_enabled) { if (!disable_standby_sig_alarm()) elog(FATAL, "could not disable timer for process wakeup"); } }