diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index dade5cc3c051c855de2d2800a4db74f0907e41bb..7c946804a5fddcd3dc905a0f0fcefc788f6ead86 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -97,6 +97,7 @@ #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/syscache.h" +#include "utils/timeout.h" #include "utils/timestamp.h" #include "utils/tqual.h" @@ -432,7 +433,7 @@ AutoVacLauncherMain(int argc, char *argv[]) pqsignal(SIGTERM, avl_sigterm_handler); pqsignal(SIGQUIT, quickdie); - pqsignal(SIGALRM, handle_sig_alarm); + InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, procsignal_sigusr1_handler); @@ -482,9 +483,9 @@ AutoVacLauncherMain(int argc, char *argv[]) /* Prevents interrupts while cleaning up */ HOLD_INTERRUPTS(); - /* Forget any pending QueryCancel request */ + /* Forget any pending QueryCancel or timeout request */ QueryCancelPending = false; - disable_sig_alarm(true); + disable_all_timeouts(false); QueryCancelPending = false; /* again in case timeout occurred */ /* Report the error to the server log */ @@ -1492,7 +1493,7 @@ AutoVacWorkerMain(int argc, char *argv[]) pqsignal(SIGINT, StatementCancelHandler); pqsignal(SIGTERM, die); pqsignal(SIGQUIT, quickdie); - pqsignal(SIGALRM, handle_sig_alarm); + InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, procsignal_sigusr1_handler); diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 45f6ac624eb09e6878403cfdc0f1b5e0e05bf389..0be3230c2a5cbf5b697c4ebf6ff98578805e140d 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -112,12 +112,12 @@ #include "storage/ipc.h" #include "storage/pg_shmem.h" #include "storage/pmsignal.h" -#include "storage/proc.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/datetime.h" #include "utils/memutils.h" #include "utils/ps_status.h" +#include "utils/timeout.h" #ifdef EXEC_BACKEND #include "storage/spin.h" @@ -337,6 +337,7 @@ static void reaper(SIGNAL_ARGS); static void sigusr1_handler(SIGNAL_ARGS); static void startup_die(SIGNAL_ARGS); static void dummy_handler(SIGNAL_ARGS); +static void StartupPacketTimeoutHandler(void); static void CleanupBackend(int pid, int exitstatus); static void HandleChildCrash(int pid, int exitstatus, const char *procname); static void LogChildExit(int lev, const char *procname, @@ -3415,7 +3416,7 @@ BackendInitialize(Port *port) */ pqsignal(SIGTERM, startup_die); pqsignal(SIGQUIT, startup_die); - pqsignal(SIGALRM, startup_die); + InitializeTimeouts(); /* establishes SIGALRM handler */ PG_SETMASK(&StartupBlockSig); /* @@ -3469,9 +3470,18 @@ BackendInitialize(Port *port) * time delay, so that a broken client can't hog a connection * indefinitely. PreAuthDelay and any DNS interactions above don't count * against the time limit. + * + * Note: AuthenticationTimeout is applied here while waiting for the + * startup packet, and then again in InitPostgres for the duration of any + * authentication operations. So a hostile client could tie up the + * process for nearly twice AuthenticationTimeout before we kick him off. + * + * Note: because PostgresMain will call InitializeTimeouts again, the + * registration of STARTUP_PACKET_TIMEOUT will be lost. This is okay + * since we never use it again after this function. */ - if (!enable_sig_alarm(AuthenticationTimeout * 1000, false)) - elog(FATAL, "could not set timer for startup packet timeout"); + RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler); + enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000); /* * Receive the startup packet (which might turn out to be a cancel request @@ -3508,8 +3518,7 @@ BackendInitialize(Port *port) /* * Disable the timeout, and prevent SIGTERM/SIGQUIT again. */ - if (!disable_sig_alarm(false)) - elog(FATAL, "could not disable timer for startup packet timeout"); + disable_timeout(STARTUP_PACKET_TIMEOUT, false); PG_SETMASK(&BlockSig); } @@ -4311,8 +4320,8 @@ sigusr1_handler(SIGNAL_ARGS) } /* - * Timeout or shutdown signal from postmaster while processing startup packet. - * Cleanup and exit(1). + * SIGTERM or SIGQUIT while processing startup packet. + * Clean up and exit(1). * * XXX: possible future improvement: try to send a message indicating * why we are disconnecting. Problem is to be sure we don't block while @@ -4339,6 +4348,17 @@ dummy_handler(SIGNAL_ARGS) { } +/* + * Timeout while processing startup packet. + * As for startup_die(), we clean up and exit(1). + */ +static void +StartupPacketTimeoutHandler(void) +{ + proc_exit(1); +} + + /* * RandomSalt */ diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c index ed75d0958e077905c8bed74b8cde01ef2551d787..ab4d1645f246aa9c56e14bf0374e69dfb4a950c3 100644 --- a/src/backend/postmaster/startup.c +++ b/src/backend/postmaster/startup.c @@ -27,8 +27,9 @@ #include "storage/ipc.h" #include "storage/latch.h" #include "storage/pmsignal.h" -#include "storage/proc.h" +#include "storage/standby.h" #include "utils/guc.h" +#include "utils/timeout.h" /* @@ -185,20 +186,12 @@ StartupProcessMain(void) /* * Properly accept or ignore signals the postmaster might send us. - * - * Note: ideally we'd not enable handle_standby_sig_alarm unless actually - * doing hot standby, but we don't know that yet. Rely on it to not do - * anything if it shouldn't. */ pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */ pqsignal(SIGINT, SIG_IGN); /* ignore query cancel */ pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */ pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */ - if (EnableHotStandby) - pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless - * InHotStandby */ - else - pqsignal(SIGALRM, SIG_IGN); + InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, StartupProcSigUsr1Handler); pqsignal(SIGUSR2, StartupProcTriggerHandler); @@ -212,11 +205,20 @@ StartupProcessMain(void) pqsignal(SIGCONT, SIG_DFL); pqsignal(SIGWINCH, SIG_DFL); + /* + * Register timeouts needed for standby mode + */ + RegisterTimeout(STANDBY_DEADLOCK_TIMEOUT, StandbyDeadLockHandler); + RegisterTimeout(STANDBY_TIMEOUT, StandbyTimeoutHandler); + /* * Unblock signals (they were blocked when the postmaster forked us) */ PG_SETMASK(&UnBlockSig); + /* + * Do what we came for. + */ StartupXLOG(); /* diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index d007f5f6fa1865cdd7be4446eecc60c33ebe1430..37a030b5f5e4e1536c44227672be05ffcff88537 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -63,6 +63,7 @@ #include "utils/memutils.h" #include "utils/ps_status.h" #include "utils/resowner.h" +#include "utils/timeout.h" #include "utils/timestamp.h" @@ -1345,7 +1346,7 @@ WalSndSignals(void) pqsignal(SIGINT, SIG_IGN); /* not used */ pqsignal(SIGTERM, WalSndShutdownHandler); /* request shutdown */ pqsignal(SIGQUIT, WalSndQuickDieHandler); /* hard crash time */ - pqsignal(SIGALRM, SIG_IGN); + InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, WalSndXLogSendHandler); /* request WAL sending */ pqsignal(SIGUSR2, WalSndLastCycleHandler); /* request a last cycle and diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c index 995b68aae553f6cbbc1dbfc0a05ddeb4404bfc7c..43f74112733772089a33e39b623572b916fe2d35 100644 --- a/src/backend/storage/ipc/standby.c +++ b/src/backend/storage/ipc/standby.c @@ -28,6 +28,7 @@ #include "storage/sinvaladt.h" #include "storage/standby.h" #include "utils/ps_status.h" +#include "utils/timeout.h" #include "utils/timestamp.h" /* User-settable GUC parameters */ @@ -40,6 +41,7 @@ static List *RecoveryLockList; static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist, ProcSignalReason reason); static void ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid); +static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason); static void LogCurrentRunningXacts(RunningTransactions CurrRunningXacts); static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks); @@ -370,13 +372,15 @@ ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid) * 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. + * The ProcWaitForSignal() sleep normally done in LockBufferForCleanup() + * (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 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. + * those 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 @@ -389,32 +393,26 @@ ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid) * * 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. + * at least deadlock_timeout. */ void ResolveRecoveryConflictWithBufferPin(void) { - bool sig_alarm_enabled = false; TimestampTz ltime; - TimestampTz now; Assert(InHotStandby); ltime = GetStandbyLimitTime(); - now = GetCurrentTimestamp(); - if (!ltime) + if (ltime == 0) { /* * We're willing to wait forever for conflicts, so set timeout for - * deadlock check (only) + * 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"); + enable_timeout_after(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout); } - else if (now >= ltime) + else if (GetCurrentTimestamp() >= ltime) { /* * We're already behind, so clear a path as quickly as possible. @@ -427,23 +425,23 @@ ResolveRecoveryConflictWithBufferPin(void) * 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"); + enable_timeout_after(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout); + enable_timeout_at(STANDBY_TIMEOUT, ltime); } /* 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"); - } + /* + * Clear any timeout requests established above. We assume here that + * the Startup process doesn't have any other 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); } -void +static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason) { Assert(reason == PROCSIG_RECOVERY_CONFLICT_BUFFERPIN || @@ -492,6 +490,38 @@ CheckRecoveryConflictDeadlock(void) errdetail("User transaction caused buffer deadlock with recovery."))); } + +/* -------------------------------- + * timeout handler routines + * -------------------------------- + */ + +/* + * StandbyDeadLockHandler() will be called if STANDBY_DEADLOCK_TIMEOUT + * occurs before STANDBY_TIMEOUT. Send out a request for hot-standby + * backends to check themselves for deadlocks. + */ +void +StandbyDeadLockHandler(void) +{ + SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK); +} + +/* + * StandbyTimeoutHandler() will be called if STANDBY_TIMEOUT is exceeded. + * Send out a request to release conflicting buffer pins unconditionally, + * so we can press ahead with applying changes in recovery. + */ +void +StandbyTimeoutHandler(void) +{ + /* forget any pending STANDBY_DEADLOCK_TIMEOUT request */ + disable_timeout(STANDBY_DEADLOCK_TIMEOUT, false); + + SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); +} + + /* * ----------------------------------------------------- * Locking in Recovery Mode diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 21598d3c18635acee2728bd54910732b34904952..0df562b30640f35ebd27e43389c0b99ecf3d87be 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -48,6 +48,7 @@ #include "storage/procarray.h" #include "storage/procsignal.h" #include "storage/spin.h" +#include "utils/timeout.h" #include "utils/timestamp.h" @@ -77,26 +78,13 @@ PGPROC *PreparedXactProcs = NULL; /* If we are waiting for a lock, this points to the associated LOCALLOCK */ static LOCALLOCK *lockAwaited = NULL; -/* Mark these volatile because they can be changed by signal handler */ -static volatile bool standby_timeout_active = false; -static volatile bool statement_timeout_active = false; -static volatile bool deadlock_timeout_active = false; +/* Mark this volatile because it can be changed by signal handler */ static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED; -volatile bool cancel_from_timeout = false; - -/* timeout_start_time is set when log_lock_waits is true */ -static TimestampTz timeout_start_time; - -/* statement_fin_time is valid only if statement_timeout_active is true */ -static TimestampTz statement_fin_time; -static TimestampTz statement_fin_time2; /* valid only in recovery */ static void RemoveProcFromArray(int code, Datum arg); static void ProcKill(int code, Datum arg); static void AuxiliaryProcKill(int code, Datum arg); -static bool CheckStatementTimeout(void); -static bool CheckStandbyTimeout(void); /* @@ -653,7 +641,7 @@ LockErrorCleanup(void) return; /* Turn off the deadlock timer, if it's still running (see ProcSleep) */ - disable_sig_alarm(false); + disable_timeout(DEADLOCK_TIMEOUT, false); /* Unlink myself from the wait queue, if on it (might not be anymore!) */ partitionLock = LockHashPartitionLock(lockAwaited->hashcode); @@ -1036,7 +1024,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) if (RecoveryInProgress() && !InRecovery) CheckRecoveryConflictDeadlock(); - /* Reset deadlock_state before enabling the signal handler */ + /* Reset deadlock_state before enabling the timeout handler */ deadlock_state = DS_NOT_YET_CHECKED; /* @@ -1048,8 +1036,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) * By delaying the check until we've waited for a bit, we can avoid * running the rather expensive deadlock-check code in most cases. */ - if (!enable_sig_alarm(DeadlockTimeout, false)) - elog(FATAL, "could not set timer for process wakeup"); + enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout); /* * If someone wakes us between LWLockRelease and PGSemaphoreLock, @@ -1065,8 +1052,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) * that we don't mind losing control to a cancel/die interrupt here. We * don't, because we have no shared-state-change work to do after being * granted the lock (the grantor did it all). We do have to worry about - * updating the locallock table, but if we lose control to an error, - * LockErrorCleanup will fix that up. + * canceling the deadlock timeout and updating the locallock table, but if + * we lose control to an error, LockErrorCleanup will fix that up. */ do { @@ -1138,7 +1125,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) DescribeLockTag(&buf, &locallock->tag.lock); modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid, lockmode); - TimestampDifference(timeout_start_time, GetCurrentTimestamp(), + TimestampDifference(get_timeout_start_time(DEADLOCK_TIMEOUT), + GetCurrentTimestamp(), &secs, &usecs); msecs = secs * 1000 + usecs / 1000; usecs = usecs % 1000; @@ -1200,8 +1188,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) /* * Disable the timer, if it's still running */ - if (!disable_sig_alarm(false)) - elog(FATAL, "could not disable timer for process wakeup"); + disable_timeout(DEADLOCK_TIMEOUT, false); /* * Re-acquire the lock table's partition lock. We have to do this to hold @@ -1334,7 +1321,7 @@ ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock) /* * CheckDeadLock * - * We only get to this routine if we got SIGALRM after DeadlockTimeout + * We only get to this routine if the DEADLOCK_TIMEOUT fired * while waiting for a lock to be released by some other process. Look * to see if there's a deadlock; if not, just return and continue waiting. * (But signal ProcSleep to log a message, if log_lock_waits is true.) @@ -1344,7 +1331,7 @@ ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock) * NB: this is run inside a signal handler, so be very wary about what is done * here or in called routines. */ -static void +void CheckDeadLock(void) { int i; @@ -1498,401 +1485,3 @@ ProcSendSignal(int pid) if (proc != NULL) PGSemaphoreUnlock(&proc->sem); } - - -/***************************************************************************** - * SIGALRM interrupt support - * - * Maybe these should be in pqsignal.c? - *****************************************************************************/ - -/* - * Enable the SIGALRM interrupt to fire after the specified delay - * - * Delay is given in milliseconds. Caller should be sure a SIGALRM - * signal handler is installed before this is called. - * - * This code properly handles nesting of deadlock timeout alarms within - * statement timeout alarms. - * - * Returns TRUE if okay, FALSE on failure. - */ -bool -enable_sig_alarm(int delayms, bool is_statement_timeout) -{ - TimestampTz fin_time; - struct itimerval timeval; - - if (is_statement_timeout) - { - /* - * Begin statement-level timeout - * - * Note that we compute statement_fin_time with reference to the - * statement_timestamp, but apply the specified delay without any - * correction; that is, we ignore whatever time has elapsed since - * statement_timestamp was set. In the normal case only a small - * interval will have elapsed and so this doesn't matter, but there - * are corner cases (involving multi-statement query strings with - * embedded COMMIT or ROLLBACK) where we might re-initialize the - * statement timeout long after initial receipt of the message. In - * such cases the enforcement of the statement timeout will be a bit - * inconsistent. This annoyance is judged not worth the cost of - * performing an additional gettimeofday() here. - */ - Assert(!deadlock_timeout_active); - fin_time = GetCurrentStatementStartTimestamp(); - fin_time = TimestampTzPlusMilliseconds(fin_time, delayms); - statement_fin_time = fin_time; - cancel_from_timeout = false; - statement_timeout_active = true; - } - else if (statement_timeout_active) - { - /* - * Begin deadlock timeout with statement-level timeout active - * - * Here, we want to interrupt at the closer of the two timeout times. - * If fin_time >= statement_fin_time then we need not touch the - * existing timer setting; else set up to interrupt at the deadlock - * timeout time. - * - * NOTE: in this case it is possible that this routine will be - * interrupted by the previously-set timer alarm. This is okay - * because the signal handler will do only what it should do according - * to the state variables. The deadlock checker may get run earlier - * than normal, but that does no harm. - */ - timeout_start_time = GetCurrentTimestamp(); - fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms); - deadlock_timeout_active = true; - if (fin_time >= statement_fin_time) - return true; - } - else - { - /* Begin deadlock timeout with no statement-level timeout */ - deadlock_timeout_active = true; - /* GetCurrentTimestamp can be expensive, so only do it if we must */ - if (log_lock_waits) - timeout_start_time = GetCurrentTimestamp(); - } - - /* If we reach here, okay to set the timer interrupt */ - MemSet(&timeval, 0, sizeof(struct itimerval)); - timeval.it_value.tv_sec = delayms / 1000; - timeval.it_value.tv_usec = (delayms % 1000) * 1000; - if (setitimer(ITIMER_REAL, &timeval, NULL)) - return false; - return true; -} - -/* - * Cancel the SIGALRM timer, either for a deadlock timeout or a statement - * timeout. If a deadlock timeout is canceled, any active statement timeout - * remains in force. - * - * Returns TRUE if okay, FALSE on failure. - */ -bool -disable_sig_alarm(bool is_statement_timeout) -{ - /* - * Always disable the interrupt if it is active; this avoids being - * interrupted by the signal handler and thereby possibly getting - * confused. - * - * We will re-enable the interrupt if necessary in CheckStatementTimeout. - */ - if (statement_timeout_active || deadlock_timeout_active) - { - struct itimerval timeval; - - MemSet(&timeval, 0, sizeof(struct itimerval)); - if (setitimer(ITIMER_REAL, &timeval, NULL)) - { - statement_timeout_active = false; - cancel_from_timeout = false; - deadlock_timeout_active = false; - return false; - } - } - - /* Always cancel deadlock timeout, in case this is error cleanup */ - deadlock_timeout_active = false; - - /* Cancel or reschedule statement timeout */ - if (is_statement_timeout) - { - statement_timeout_active = false; - cancel_from_timeout = false; - } - else if (statement_timeout_active) - { - if (!CheckStatementTimeout()) - return false; - } - return true; -} - - -/* - * Check for statement timeout. If the timeout time has come, - * trigger a query-cancel interrupt; if not, reschedule the SIGALRM - * interrupt to occur at the right time. - * - * Returns true if okay, false if failed to set the interrupt. - */ -static bool -CheckStatementTimeout(void) -{ - TimestampTz now; - - if (!statement_timeout_active) - return true; /* do nothing if not active */ - - now = GetCurrentTimestamp(); - - if (now >= statement_fin_time) - { - /* Time to die */ - statement_timeout_active = false; - cancel_from_timeout = true; -#ifdef HAVE_SETSID - /* try to signal whole process group */ - kill(-MyProcPid, SIGINT); -#endif - kill(MyProcPid, SIGINT); - } - else - { - /* Not time yet, so (re)schedule the interrupt */ - long secs; - int usecs; - struct itimerval timeval; - - TimestampDifference(now, statement_fin_time, - &secs, &usecs); - - /* - * It's possible that the difference is less than a microsecond; - * ensure we don't cancel, rather than set, the interrupt. - */ - if (secs == 0 && usecs == 0) - usecs = 1; - MemSet(&timeval, 0, sizeof(struct itimerval)); - timeval.it_value.tv_sec = secs; - timeval.it_value.tv_usec = usecs; - if (setitimer(ITIMER_REAL, &timeval, NULL)) - return false; - } - - return true; -} - - -/* - * Signal handler for SIGALRM for normal user backends - * - * Process deadlock check and/or statement timeout check, as needed. - * To avoid various edge cases, we must be careful to do nothing - * when there is nothing to be done. We also need to be able to - * reschedule the timer interrupt if called before end of statement. - */ -void -handle_sig_alarm(SIGNAL_ARGS) -{ - int save_errno = errno; - - /* SIGALRM is cause for waking anything waiting on the process latch */ - if (MyProc) - SetLatch(&MyProc->procLatch); - - if (deadlock_timeout_active) - { - deadlock_timeout_active = false; - CheckDeadLock(); - } - - if (statement_timeout_active) - (void) CheckStatementTimeout(); - - errno = save_errno; -} - -/* - * Signal handler for SIGALRM in Startup process - * - * To avoid various edge cases, we must be careful to do nothing - * when there is nothing to be done. We also need to be able to - * reschedule the timer interrupt if called before end of statement. - * - * We set either deadlock_timeout_active or statement_timeout_active - * or both. Interrupts are enabled if standby_timeout_active. - */ -bool -enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only) -{ - TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now, - DeadlockTimeout); - - if (deadlock_only) - { - /* - * Wake up at deadlock_time only, then wait forever - */ - statement_fin_time = deadlock_time; - deadlock_timeout_active = true; - statement_timeout_active = false; - } - else if (fin_time > deadlock_time) - { - /* - * Wake up at deadlock_time, then again at fin_time - */ - statement_fin_time = deadlock_time; - statement_fin_time2 = fin_time; - deadlock_timeout_active = true; - statement_timeout_active = true; - } - else - { - /* - * Wake only at fin_time because its fairly soon - */ - statement_fin_time = fin_time; - deadlock_timeout_active = false; - statement_timeout_active = true; - } - - if (deadlock_timeout_active || statement_timeout_active) - { - long secs; - int usecs; - struct itimerval timeval; - - TimestampDifference(now, statement_fin_time, - &secs, &usecs); - if (secs == 0 && usecs == 0) - usecs = 1; - MemSet(&timeval, 0, sizeof(struct itimerval)); - timeval.it_value.tv_sec = secs; - timeval.it_value.tv_usec = usecs; - if (setitimer(ITIMER_REAL, &timeval, NULL)) - return false; - standby_timeout_active = true; - } - - return true; -} - -bool -disable_standby_sig_alarm(void) -{ - /* - * Always disable the interrupt if it is active; this avoids being - * interrupted by the signal handler and thereby possibly getting - * confused. - * - * We will re-enable the interrupt if necessary in CheckStandbyTimeout. - */ - if (standby_timeout_active) - { - struct itimerval timeval; - - MemSet(&timeval, 0, sizeof(struct itimerval)); - if (setitimer(ITIMER_REAL, &timeval, NULL)) - { - standby_timeout_active = false; - return false; - } - } - - standby_timeout_active = false; - - return true; -} - -/* - * CheckStandbyTimeout() runs unconditionally in the Startup process - * SIGALRM handler. Timers will only be set when InHotStandby. - * We simply ignore any signals unless the timer has been set. - */ -static bool -CheckStandbyTimeout(void) -{ - TimestampTz now; - bool reschedule = false; - - standby_timeout_active = false; - - now = GetCurrentTimestamp(); - - /* - * Reschedule the timer if its not time to wake yet, or if we have both - * timers set and the first one has just been reached. - */ - if (now >= statement_fin_time) - { - if (deadlock_timeout_active) - { - /* - * We're still waiting when we reach deadlock timeout, so send out - * a request to have other backends check themselves for deadlock. - * Then continue waiting until statement_fin_time, if that's set. - */ - SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK); - deadlock_timeout_active = false; - - /* - * Begin second waiting period if required. - */ - if (statement_timeout_active) - { - reschedule = true; - statement_fin_time = statement_fin_time2; - } - } - else - { - /* - * We've now reached statement_fin_time, so ask all conflicts to - * leave, so we can press ahead with applying changes in recovery. - */ - SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); - } - } - else - reschedule = true; - - if (reschedule) - { - long secs; - int usecs; - struct itimerval timeval; - - TimestampDifference(now, statement_fin_time, - &secs, &usecs); - if (secs == 0 && usecs == 0) - usecs = 1; - MemSet(&timeval, 0, sizeof(struct itimerval)); - timeval.it_value.tv_sec = secs; - timeval.it_value.tv_usec = usecs; - if (setitimer(ITIMER_REAL, &timeval, NULL)) - return false; - standby_timeout_active = true; - } - - return true; -} - -void -handle_standby_sig_alarm(SIGNAL_ARGS) -{ - int save_errno = errno; - - if (standby_timeout_active) - (void) CheckStandbyTimeout(); - - errno = save_errno; -} diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index f696375cabc5d5e2ddbd3689aaebc13616fddac8..37dfa18c1d0b793d096f662597b4b0a2687a1fd6 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -72,6 +72,7 @@ #include "utils/memutils.h" #include "utils/ps_status.h" #include "utils/snapmgr.h" +#include "utils/timeout.h" #include "utils/timestamp.h" #include "mb/pg_wchar.h" @@ -2396,9 +2397,9 @@ start_xact_command(void) /* Set statement timeout running, if any */ /* NB: this mustn't be enabled until we are within an xact */ if (StatementTimeout > 0) - enable_sig_alarm(StatementTimeout, true); + enable_timeout_after(STATEMENT_TIMEOUT, StatementTimeout); else - cancel_from_timeout = false; + disable_timeout(STATEMENT_TIMEOUT, false); xact_started = true; } @@ -2410,7 +2411,7 @@ finish_xact_command(void) if (xact_started) { /* Cancel any active statement timeout before committing */ - disable_sig_alarm(true); + disable_timeout(STATEMENT_TIMEOUT, false); /* Now commit the command */ ereport(DEBUG3, @@ -2891,7 +2892,7 @@ ProcessInterrupts(void) (errcode(ERRCODE_QUERY_CANCELED), errmsg("canceling authentication due to timeout"))); } - if (cancel_from_timeout) + if (get_timeout_indicator(STATEMENT_TIMEOUT)) { ImmediateInterruptOK = false; /* not idle anymore */ DisableNotifyInterrupt(); @@ -3614,7 +3615,7 @@ PostgresMain(int argc, char *argv[], const char *username) pqsignal(SIGQUIT, quickdie); /* hard crash time */ else pqsignal(SIGQUIT, die); /* cancel current query and exit */ - pqsignal(SIGALRM, handle_sig_alarm); /* timeout conditions */ + InitializeTimeouts(); /* establishes SIGALRM handler */ /* * Ignore failure to write to frontend. Note: if frontend closes @@ -3802,10 +3803,10 @@ PostgresMain(int argc, char *argv[], const char *username) /* * Forget any pending QueryCancel request, since we're returning to - * the idle loop anyway, and cancel the statement timer if running. + * the idle loop anyway, and cancel any active timeout requests. */ QueryCancelPending = false; - disable_sig_alarm(true); + disable_all_timeouts(false); QueryCancelPending = false; /* again in case timeout occurred */ /* diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 4d4a895657ed8c67707340efa662c89a25df226f..6a3fc6f693f66304a487032e2099d1660590d836 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -41,7 +41,6 @@ #include "storage/fd.h" #include "storage/ipc.h" #include "storage/lmgr.h" -#include "storage/proc.h" #include "storage/procarray.h" #include "storage/procsignal.h" #include "storage/proc.h" @@ -56,6 +55,7 @@ #include "utils/ps_status.h" #include "utils/snapmgr.h" #include "utils/syscache.h" +#include "utils/timeout.h" #include "utils/tqual.h" @@ -65,6 +65,7 @@ static void PerformAuthentication(Port *port); static void CheckMyDatabase(const char *name, bool am_superuser); static void InitCommunication(void); static void ShutdownPostgres(int code, Datum arg); +static void StatementTimeoutHandler(void); static bool ThereIsAtLeastOneRole(void); static void process_startup_options(Port *port, bool am_superuser); static void process_settings(Oid databaseid, Oid roleid); @@ -205,8 +206,7 @@ PerformAuthentication(Port *port) * during authentication. Since we're inside a transaction and might do * database access, we have to use the statement_timeout infrastructure. */ - if (!enable_sig_alarm(AuthenticationTimeout * 1000, true)) - elog(FATAL, "could not set timer for authorization timeout"); + enable_timeout_after(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000); /* * Now perform authentication exchange. @@ -216,8 +216,7 @@ PerformAuthentication(Port *port) /* * Done with authentication. Disable the timeout, and log if needed. */ - if (!disable_sig_alarm(true)) - elog(FATAL, "could not disable timer for authorization timeout"); + disable_timeout(STATEMENT_TIMEOUT, false); if (Log_connections) { @@ -495,6 +494,16 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username, /* Now that we have a BackendId, we can participate in ProcSignal */ ProcSignalInit(MyBackendId); + /* + * Also set up timeout handlers needed for backend operation. We need + * these in every case except bootstrap. + */ + if (!bootstrap) + { + RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLock); + RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler); + } + /* * bufmgr needs another initialization call too */ @@ -974,6 +983,20 @@ ShutdownPostgres(int code, Datum arg) } +/* + * STATEMENT_TIMEOUT handler: trigger a query-cancel interrupt. + */ +static void +StatementTimeoutHandler(void) +{ +#ifdef HAVE_SETSID + /* try to signal whole process group */ + kill(-MyProcPid, SIGINT); +#endif + kill(MyProcPid, SIGINT); +} + + /* * Returns true if at least one role is defined in this database cluster. */ diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile index cd9ba5d1cc235b2e3d8021ce75f9ec1ba5a106c7..08be3bd699d9aa450b08e2eacff2ac084178ba0b 100644 --- a/src/backend/utils/misc/Makefile +++ b/src/backend/utils/misc/Makefile @@ -14,8 +14,8 @@ include $(top_builddir)/src/Makefile.global override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS) -OBJS = guc.o help_config.o pg_rusage.o ps_status.o superuser.o tzparser.o \ - rbtree.o +OBJS = guc.o help_config.o pg_rusage.o ps_status.o rbtree.o \ + superuser.o timeout.o tzparser.o # This location might depend on the installation directories. Therefore # we can't subsitute it into pg_config.h. diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c new file mode 100644 index 0000000000000000000000000000000000000000..668d5f3b590dfdb37f7b3295da2186a4f5e655a5 --- /dev/null +++ b/src/backend/utils/misc/timeout.c @@ -0,0 +1,479 @@ +/*------------------------------------------------------------------------- + * + * timeout.c + * Routines to multiplex SIGALRM interrupts for multiple timeout reasons. + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/timeout.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/time.h> + +#include "libpq/pqsignal.h" +#include "storage/proc.h" +#include "utils/timeout.h" +#include "utils/timestamp.h" + + +/* Data about any one timeout reason */ +typedef struct timeout_params +{ + TimeoutId index; /* identifier of timeout reason */ + + /* volatile because it may be changed from the signal handler */ + volatile bool indicator; /* true if timeout has occurred */ + + /* callback function for timeout, or NULL if timeout not registered */ + timeout_handler timeout_handler; + + TimestampTz start_time; /* time that timeout was last activated */ + TimestampTz fin_time; /* if active, time it is due to fire */ +} timeout_params; + +/* + * List of possible timeout reasons in the order of enum TimeoutId. + */ +static timeout_params all_timeouts[MAX_TIMEOUTS]; +static bool all_timeouts_initialized = false; + +/* + * List of active timeouts ordered by their fin_time and priority. + * This list is subject to change by the interrupt handler, so it's volatile. + */ +static volatile int num_active_timeouts = 0; +static timeout_params *volatile active_timeouts[MAX_TIMEOUTS]; + + +/***************************************************************************** + * Internal helper functions + * + * For all of these, it is caller's responsibility to protect them from + * interruption by the signal handler. + *****************************************************************************/ + +/* + * Find the index of a given timeout reason in the active array. + * If it's not there, return -1. + */ +static int +find_active_timeout(TimeoutId id) +{ + int i; + + for (i = 0; i < num_active_timeouts; i++) + { + if (active_timeouts[i]->index == id) + return i; + } + + return -1; +} + +/* + * Insert specified timeout reason into the list of active timeouts + * at the given index. + */ +static void +insert_timeout(TimeoutId id, int index) +{ + int i; + + if (index < 0 || index > num_active_timeouts) + elog(FATAL, "timeout index %d out of range 0..%d", index, + num_active_timeouts); + + for (i = num_active_timeouts - 1; i >= index; i--) + active_timeouts[i + 1] = active_timeouts[i]; + + active_timeouts[index] = &all_timeouts[id]; + + /* NB: this must be the last step, see comments in enable_timeout */ + num_active_timeouts++; +} + +/* + * Remove the index'th element from the timeout list. + */ +static void +remove_timeout_index(int index) +{ + int i; + + if (index < 0 || index >= num_active_timeouts) + elog(FATAL, "timeout index %d out of range 0..%d", index, + num_active_timeouts - 1); + + for (i = index + 1; i < num_active_timeouts; i++) + active_timeouts[i - 1] = active_timeouts[i]; + + num_active_timeouts--; +} + +/* + * Schedule alarm for the next active timeout, if any + * + * We assume the caller has obtained the current time, or a close-enough + * approximation. + */ +static void +schedule_alarm(TimestampTz now) +{ + if (num_active_timeouts > 0) + { + struct itimerval timeval; + long secs; + int usecs; + + MemSet(&timeval, 0, sizeof(struct itimerval)); + + /* Get the time remaining till the nearest pending timeout */ + TimestampDifference(now, active_timeouts[0]->fin_time, + &secs, &usecs); + + /* + * It's possible that the difference is less than a microsecond; + * ensure we don't cancel, rather than set, the interrupt. + */ + if (secs == 0 && usecs == 0) + usecs = 1; + + timeval.it_value.tv_sec = secs; + timeval.it_value.tv_usec = usecs; + + /* Set the alarm timer */ + if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) + elog(FATAL, "could not enable SIGALRM timer: %m"); + } +} + + +/***************************************************************************** + * Signal handler + *****************************************************************************/ + +/* + * Signal handler for SIGALRM + * + * Process any active timeout reasons and then reschedule the interrupt + * as needed. + */ +static void +handle_sig_alarm(SIGNAL_ARGS) +{ + int save_errno = errno; + + /* + * SIGALRM is always cause for waking anything waiting on the process + * latch. Cope with MyProc not being there, as the startup process also + * uses this signal handler. + */ + if (MyProc) + SetLatch(&MyProc->procLatch); + + /* + * Fire any pending timeouts. + */ + if (num_active_timeouts > 0) + { + TimestampTz now = GetCurrentTimestamp(); + + /* While the first pending timeout has been reached ... */ + while (num_active_timeouts > 0 && + now >= active_timeouts[0]->fin_time) + { + timeout_params *this_timeout = active_timeouts[0]; + + /* Remove it from the active list */ + remove_timeout_index(0); + + /* Mark it as fired */ + this_timeout->indicator = true; + + /* And call its handler function */ + (*this_timeout->timeout_handler) (); + + /* + * The handler might not take negligible time (CheckDeadLock for + * instance isn't too cheap), so let's update our idea of "now" + * after each one. + */ + now = GetCurrentTimestamp(); + } + + /* Done firing timeouts, so reschedule next interrupt if any */ + schedule_alarm(now); + } + + errno = save_errno; +} + + +/***************************************************************************** + * Public API + *****************************************************************************/ + +/* + * Initialize timeout module. + * + * This must be called in every process that wants to use timeouts. + * + * If the process was forked from another one that was also using this + * module, be sure to call this before re-enabling signals; else handlers + * meant to run in the parent process might get invoked in this one. + */ +void +InitializeTimeouts(void) +{ + int i; + + /* Initialize, or re-initialize, all local state */ + num_active_timeouts = 0; + + for (i = 0; i < MAX_TIMEOUTS; i++) + { + all_timeouts[i].index = i; + all_timeouts[i].indicator = false; + all_timeouts[i].timeout_handler = NULL; + all_timeouts[i].start_time = 0; + all_timeouts[i].fin_time = 0; + } + + all_timeouts_initialized = true; + + /* Now establish the signal handler */ + pqsignal(SIGALRM, handle_sig_alarm); +} + +/* + * Register a timeout reason + * + * For predefined timeouts, this just registers the callback function. + * + * For user-defined timeouts, pass id == USER_TIMEOUT; we then allocate and + * return a timeout ID. + */ +TimeoutId +RegisterTimeout(TimeoutId id, timeout_handler handler) +{ + Assert(all_timeouts_initialized); + + if (id >= USER_TIMEOUT) + { + /* Allocate a user-defined timeout reason */ + for (id = USER_TIMEOUT; id < MAX_TIMEOUTS; id++) + if (all_timeouts[id].timeout_handler == NULL) + break; + if (id >= MAX_TIMEOUTS) + ereport(FATAL, + (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED), + errmsg("cannot add more timeout reasons"))); + } + + Assert(all_timeouts[id].timeout_handler == NULL); + + all_timeouts[id].timeout_handler = handler; + + return id; +} + +/* + * Enable the specified timeout reason + */ +static void +enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time) +{ + struct itimerval timeval; + int i; + + /* Assert request is sane */ + Assert(all_timeouts_initialized); + Assert(all_timeouts[id].timeout_handler != NULL); + + /* + * Disable the timer if it is active; this avoids getting interrupted by + * the signal handler and thereby possibly getting confused. We will + * re-enable the interrupt below. + * + * If num_active_timeouts is zero, we don't have to call setitimer. There + * should not be any pending interrupt, and even if there is, the worst + * possible case is that the signal handler fires during schedule_alarm. + * (If it fires at any point before insert_timeout has incremented + * num_active_timeouts, it will do nothing.) In that case we could end up + * scheduling a useless interrupt ... but when the interrupt does happen, + * the signal handler will do nothing, so it's all good. + */ + if (num_active_timeouts > 0) + { + MemSet(&timeval, 0, sizeof(struct itimerval)); + if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) + elog(FATAL, "could not disable SIGALRM timer: %m"); + } + + /* + * If this timeout was already active, momentarily disable it. We + * interpret the call as a directive to reschedule the timeout. + */ + i = find_active_timeout(id); + if (i >= 0) + remove_timeout_index(i); + + /* + * Find out the index where to insert the new timeout. We sort by + * fin_time, and for equal fin_time by priority. + */ + for (i = 0; i < num_active_timeouts; i++) + { + timeout_params *old_timeout = active_timeouts[i]; + + if (fin_time < old_timeout->fin_time) + break; + if (fin_time == old_timeout->fin_time && id < old_timeout->index) + break; + } + + /* + * Activate the timeout. + */ + all_timeouts[id].indicator = false; + all_timeouts[id].start_time = now; + all_timeouts[id].fin_time = fin_time; + insert_timeout(id, i); + + /* + * Set the timer. + */ + schedule_alarm(now); +} + +/* + * Enable the specified timeout to fire after the specified delay. + * + * Delay is given in milliseconds. + */ +void +enable_timeout_after(TimeoutId id, int delay_ms) +{ + TimestampTz now; + TimestampTz fin_time; + + now = GetCurrentTimestamp(); + fin_time = TimestampTzPlusMilliseconds(now, delay_ms); + + enable_timeout(id, now, fin_time); +} + +/* + * Enable the specified timeout to fire at the specified time. + * + * This is provided to support cases where there's a reason to calculate + * the timeout by reference to some point other than "now". If there isn't, + * use enable_timeout_after(), to avoid calling GetCurrentTimestamp() twice. + */ +void +enable_timeout_at(TimeoutId id, TimestampTz fin_time) +{ + enable_timeout(id, GetCurrentTimestamp(), fin_time); +} + +/* + * Cancel the specified timeout. + * + * The timeout's I've-been-fired indicator is reset, + * unless keep_indicator is true. + * + * When a timeout is canceled, any other active timeout remains in force. + * It's not an error to disable a timeout that is not enabled. + */ +void +disable_timeout(TimeoutId id, bool keep_indicator) +{ + struct itimerval timeval; + int i; + + /* Assert request is sane */ + Assert(all_timeouts_initialized); + Assert(all_timeouts[id].timeout_handler != NULL); + + /* + * Disable the timer if it is active; this avoids getting interrupted by + * the signal handler and thereby possibly getting confused. We will + * re-enable the interrupt if necessary below. + * + * If num_active_timeouts is zero, we don't have to call setitimer. There + * should not be any pending interrupt, and even if there is, the signal + * handler will not do anything. In this situation the only thing we + * really have to do is reset the timeout's indicator. + */ + if (num_active_timeouts > 0) + { + MemSet(&timeval, 0, sizeof(struct itimerval)); + if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) + elog(FATAL, "could not disable SIGALRM timer: %m"); + } + + /* Find the timeout and remove it from the active list. */ + i = find_active_timeout(id); + if (i >= 0) + remove_timeout_index(i); + + /* Mark it inactive, whether it was active or not. */ + if (!keep_indicator) + all_timeouts[id].indicator = false; + + /* Now re-enable the timer, if necessary. */ + if (num_active_timeouts > 0) + schedule_alarm(GetCurrentTimestamp()); +} + +/* + * Disable SIGALRM and remove all timeouts from the active list, + * and optionally reset their timeout indicators. + */ +void +disable_all_timeouts(bool keep_indicators) +{ + struct itimerval timeval; + int i; + + MemSet(&timeval, 0, sizeof(struct itimerval)); + if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) + elog(FATAL, "could not disable SIGALRM timer: %m"); + + num_active_timeouts = 0; + + if (!keep_indicators) + { + for (i = 0; i < MAX_TIMEOUTS; i++) + all_timeouts[i].indicator = false; + } +} + +/* + * Return the timeout's I've-been-fired indicator + */ +bool +get_timeout_indicator(TimeoutId id) +{ + return all_timeouts[id].indicator; +} + +/* + * Return the time when the timeout was most recently activated + * + * Note: will return 0 if timeout has never been activated in this process. + * However, we do *not* reset the start_time when a timeout occurs, so as + * not to create a race condition if SIGALRM fires just as some code is + * about to fetch the value. + */ +TimestampTz +get_timeout_start_time(TimeoutId id) +{ + return all_timeouts[id].start_time; +} diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 31f7099a635b12886444fa30c18ac9ebc62e73b1..e10aafe99e43d19d2346e5a43b240ffa339808dc 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -15,7 +15,6 @@ #define _PROC_H_ #include "access/xlogdefs.h" -#include "datatype/timestamp.h" #include "storage/latch.h" #include "storage/lock.h" #include "storage/pg_sema.h" @@ -222,8 +221,6 @@ extern int DeadlockTimeout; extern int StatementTimeout; extern bool log_lock_waits; -extern volatile bool cancel_from_timeout; - /* * Function Prototypes @@ -246,19 +243,11 @@ extern void ProcQueueInit(PROC_QUEUE *queue); extern int ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable); extern PGPROC *ProcWakeup(PGPROC *proc, int waitStatus); extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock); +extern void CheckDeadLock(void); extern bool IsWaitingForLock(void); extern void LockErrorCleanup(void); extern void ProcWaitForSignal(void); extern void ProcSendSignal(int pid); -extern bool enable_sig_alarm(int delayms, bool is_statement_timeout); -extern bool disable_sig_alarm(bool is_statement_timeout); -extern void handle_sig_alarm(SIGNAL_ARGS); - -extern bool enable_standby_sig_alarm(TimestampTz now, - TimestampTz fin_time, bool deadlock_only); -extern bool disable_standby_sig_alarm(void); -extern void handle_standby_sig_alarm(SIGNAL_ARGS); - #endif /* PROC_H */ diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h index ed3b66b35df07553a77a17619e54d52b4a981562..7024fc4f3c2d2d105ab6d5898b5e362a59937148 100644 --- a/src/include/storage/standby.h +++ b/src/include/storage/standby.h @@ -33,8 +33,9 @@ extern void ResolveRecoveryConflictWithTablespace(Oid tsid); extern void ResolveRecoveryConflictWithDatabase(Oid dbid); extern void ResolveRecoveryConflictWithBufferPin(void); -extern void SendRecoveryConflictWithBufferPin(ProcSignalReason reason); extern void CheckRecoveryConflictDeadlock(void); +extern void StandbyDeadLockHandler(void); +extern void StandbyTimeoutHandler(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 0000000000000000000000000000000000000000..76a7e1a63e468c8aebf96750ce8ebfcffb72278f --- /dev/null +++ b/src/include/utils/timeout.h @@ -0,0 +1,54 @@ +/*------------------------------------------------------------------------- + * + * timeout.h + * Routines to multiplex SIGALRM interrupts for multiple timeout reasons. + * + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/timeout.h + * + *------------------------------------------------------------------------- + */ +#ifndef TIMEOUT_H +#define TIMEOUT_H + +#include "datatype/timestamp.h" + +/* + * Identifiers for timeout reasons. Note that in case multiple timeouts + * trigger at the same time, they are serviced in the order of this enum. + */ +typedef enum TimeoutId +{ + /* Predefined timeout reasons */ + STARTUP_PACKET_TIMEOUT, + DEADLOCK_TIMEOUT, + STATEMENT_TIMEOUT, + STANDBY_DEADLOCK_TIMEOUT, + STANDBY_TIMEOUT, + /* First user-definable timeout reason */ + USER_TIMEOUT, + /* Maximum number of timeout reasons */ + MAX_TIMEOUTS = 16 +} TimeoutId; + +/* callback function signature */ +typedef void (*timeout_handler) (void); + +/* timeout setup */ +extern void InitializeTimeouts(void); +extern TimeoutId RegisterTimeout(TimeoutId id, timeout_handler handler); + +/* timeout operation */ +extern void enable_timeout_after(TimeoutId id, int delay_ms); +extern void enable_timeout_at(TimeoutId id, TimestampTz fin_time); +extern void disable_timeout(TimeoutId id, bool keep_indicator); +extern void disable_all_timeouts(bool keep_indicators); + +/* accessors */ +extern bool get_timeout_indicator(TimeoutId id); +extern TimestampTz get_timeout_start_time(TimeoutId id); + +#endif /* TIMEOUT_H */