diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index dade5cc..bfa6f49 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -89,6 +89,7 @@ #include "storage/proc.h" #include "storage/procsignal.h" #include "storage/sinvaladt.h" +#include "storage/timeout.h" #include "tcop/tcopprot.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" @@ -484,7 +485,7 @@ AutoVacLauncherMain(int argc, char *argv[]) /* Forget any pending QueryCancel 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 */ diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 913734f..89ac0a0 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -112,7 +112,7 @@ #include "storage/ipc.h" #include "storage/pg_shmem.h" #include "storage/pmsignal.h" -#include "storage/proc.h" +#include "storage/timeout.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/datetime.h" @@ -3488,8 +3488,7 @@ BackendInitialize(Port *port) * indefinitely. PreAuthDelay and any DNS interactions above don't count * against the time limit. */ - if (!enable_sig_alarm(AuthenticationTimeout * 1000, false)) - elog(FATAL, "could not set timer for startup packet timeout"); + enable_timeout(AUTHENTICATION_TIMEOUT, AuthenticationTimeout * 1000); /* * Receive the startup packet (which might turn out to be a cancel request @@ -3526,8 +3525,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(AUTHENTICATION_TIMEOUT, false); PG_SETMASK(&BlockSig); } diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c index ed75d09..f9a6cf9 100644 --- a/src/backend/postmaster/startup.c +++ b/src/backend/postmaster/startup.c @@ -27,7 +27,7 @@ #include "storage/ipc.h" #include "storage/latch.h" #include "storage/pmsignal.h" -#include "storage/proc.h" +#include "storage/timeout.h" #include "utils/guc.h" @@ -195,8 +195,8 @@ StartupProcessMain(void) pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */ pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */ if (EnableHotStandby) - pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless - * InHotStandby */ + pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless + * InHotStandby */ else pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c index 995b68a..6b8233f 100644 --- a/src/backend/storage/ipc/standby.c +++ b/src/backend/storage/ipc/standby.c @@ -27,6 +27,7 @@ #include "storage/procarray.h" #include "storage/sinvaladt.h" #include "storage/standby.h" +#include "storage/timeout.h" #include "utils/ps_status.h" #include "utils/timestamp.h" @@ -394,7 +395,6 @@ ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid) void ResolveRecoveryConflictWithBufferPin(void) { - bool sig_alarm_enabled = false; TimestampTz ltime; TimestampTz now; @@ -409,10 +409,7 @@ ResolveRecoveryConflictWithBufferPin(void) * 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"); + enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout); } else if (now >= ltime) { @@ -423,24 +420,26 @@ ResolveRecoveryConflictWithBufferPin(void) } else { + long secs, + msecs; + int usecs; + /* * 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(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout); + + TimestampDifference(now, ltime, &secs, &usecs); + msecs = secs * 1000 + usecs / 1000; + + enable_timeout(STANDBY_TIMEOUT, msecs); } /* 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"); - } + disable_all_timeouts(false); } void diff --git a/src/backend/storage/lmgr/Makefile b/src/backend/storage/lmgr/Makefile index e12a854..f0f7814 100644 --- a/src/backend/storage/lmgr/Makefile +++ b/src/backend/storage/lmgr/Makefile @@ -12,7 +12,7 @@ subdir = src/backend/storage/lmgr top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o +OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 21598d3..daf4c5c 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -48,12 +48,11 @@ #include "storage/procarray.h" #include "storage/procsignal.h" #include "storage/spin.h" +#include "storage/timeout.h" #include "utils/timestamp.h" /* GUC variables */ -int DeadlockTimeout = 1000; -int StatementTimeout = 0; bool log_lock_waits = false; /* Pointer to this process's PGPROC and PGXACT structs, if any */ @@ -77,27 +76,12 @@ 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; -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 */ - +/* Mark this volatile because it can be changed by signal handler */ +static volatile DeadLockState deadlock_state; 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); - /* * Report shared-memory space needed by InitProcGlobal. @@ -653,7 +637,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); @@ -1048,8 +1032,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(DEADLOCK_TIMEOUT, DeadlockTimeout); /* * If someone wakes us between LWLockRelease and PGSemaphoreLock, @@ -1138,7 +1121,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(DEADLOCK_TIMEOUT), + GetCurrentTimestamp(), &secs, &usecs); msecs = secs * 1000 + usecs / 1000; usecs = usecs % 1000; @@ -1200,8 +1184,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 @@ -1344,7 +1327,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; @@ -1499,318 +1482,29 @@ ProcSendSignal(int pid) 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. + * trigger a query-cancel interrupt. */ -static bool -CheckStatementTimeout(void) +void +StatementTimeoutFunction(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); + /* 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; + kill(MyProcPid, SIGINT); } - /* - * 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. + * CheckStandbyDeadLock() will trigger if the deadlock timeout + * happens earlier than StandbyTimeout. If it triggers, + * StandbyTimeout will still be rescheduled. */ void -handle_sig_alarm(SIGNAL_ARGS) +StandbyDeadLockFunction(void) { - 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; + SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK); } /* @@ -1818,81 +1512,8 @@ disable_standby_sig_alarm(void) * 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) +StandbyTimeoutFunction(void) { - int save_errno = errno; - - if (standby_timeout_active) - (void) CheckStandbyTimeout(); - - errno = save_errno; + SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); } diff --git a/src/backend/storage/lmgr/timeout.c b/src/backend/storage/lmgr/timeout.c new file mode 100644 index 0000000..ebed1b3 --- /dev/null +++ b/src/backend/storage/lmgr/timeout.c @@ -0,0 +1,423 @@ +/*------------------------------------------------------------------------- + * + * timeout.c + * routines to manage timeout sources handled by SIGALRM + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/storage/lmgr/timeout.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "access/xact.h" +#include "storage/proc.h" +#include "storage/timeout.h" +#include "utils/timestamp.h" + +/* GUC variables */ +int DeadlockTimeout = 1000; +int StatementTimeout = 0; + +/* + * Infrastructure for timeouts + */ + +static void InitTimeout(TimeoutName tn, TimestampTz start_time, + TimestampTz fin_time); +static void DestroyTimeout(TimeoutName tn, bool keep_indicator); +static void DummyTimeoutFunction(void); + +typedef void (*timeout_func)(void); +typedef TimestampTz (*timeout_start)(void); + +typedef struct timeout_params +{ + TimeoutName index; + bool resched_next; + + /* volatile because it may be changed from the signal handler */ + volatile bool indicator; + + timeout_func timeout_func; + timeout_start timeout_start; + + TimestampTz start_time; + TimestampTz fin_time; +} timeout_params; + +/* + * List of possible timeout reasons in the order of enum TimeoutName. + * The priority of timeouts (in case two of them would trigger at the + * same time) is determined by this order: the earlier one in the list + * has higher priority. + */ +static timeout_params base_timeouts[TIMEOUT_MAX] = { + { + AUTHENTICATION_TIMEOUT, false, false, + DummyTimeoutFunction, GetCurrentTimestamp + }, + + { + DEADLOCK_TIMEOUT, true, false, + CheckDeadLock, GetCurrentTimestamp + }, + + { + STATEMENT_TIMEOUT, false, false, + StatementTimeoutFunction, GetCurrentStatementStartTimestamp + }, + + { + STANDBY_DEADLOCK_TIMEOUT, true, false, + StandbyDeadLockFunction, GetCurrentTimestamp + }, + + { + STANDBY_TIMEOUT, false, false, + StandbyTimeoutFunction, GetCurrentTimestamp + } +}; + +/* + * List of active timeouts ordered by their fin_time and priority. + */ +static int n_timeouts = 0; +static timeout_params *timeouts[TIMEOUT_MAX]; + +/***************************************************************************** + * Internal helper functions + *****************************************************************************/ + +/* + * Find the index of a given timeout type in the active array + */ +static int +find_active_timeout(TimeoutName tn) +{ + int i; + + for (i = 0; i < n_timeouts; i++) + { + if (timeouts[i]->index == tn) + return i; + } + + return -1; +} + +#define is_timeout_active(tn) (find_active_timeout(tn) >= 0) + +/* + * Insert tn'th timeout into the list of timeouts at the given index + * if the previous timeout allows rescheduling the next one in the list. + */ +static void +insert_timeout(TimeoutName tn, int index) +{ + int i; + + if (index < 0 || index > n_timeouts) + elog(FATAL, "timeout index %d out of range 0..%d", index, n_timeouts); + + if (index > 0 && !timeouts[index - 1]->resched_next) + return; + + for (i = n_timeouts - 1; i >= index; i--) + timeouts[i + 1] = timeouts[i]; + + timeouts[index] = &base_timeouts[tn]; + n_timeouts++; +} + +/* + * Remove the index'th element from the timeout list. + */ +static void +remove_timeout_index(int index) +{ + int i; + + if (index < 0 || index > n_timeouts) + elog(FATAL, "timeout index %d out of range 0..%d", index, n_timeouts); + + for (i = index + 1; i < n_timeouts; i++) + timeouts[i - 1] = timeouts[i]; + + if (n_timeouts > 0 && index < n_timeouts) + n_timeouts--; +} + +/* + * (Re)schedule the next active timeout + */ +static void +schedule_timeout(TimestampTz now) +{ + long secs; + int usecs; + struct itimerval timeval; + + /* There is no active timeout, do nothing. */ + if (n_timeouts == 0) + return; + + TimestampDifference(now, 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; + + MemSet(&timeval, 0, sizeof(struct itimerval)); + timeval.it_value.tv_sec = secs; + timeval.it_value.tv_usec = usecs; + if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) + elog(FATAL, "could not enable timer"); +} + +/***************************************************************************** + * Init, Destroy and callback functions for different timeouts. + *****************************************************************************/ + +/* + * Common Init and Destroy functions + */ + +static void +InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time) +{ + base_timeouts[tn].indicator = false; + base_timeouts[tn].start_time = start_time; + base_timeouts[tn].fin_time = fin_time; +} + +static void +DestroyTimeout(TimeoutName tn, bool keep_indicator) +{ + if (!keep_indicator) + base_timeouts[tn].indicator = false; + base_timeouts[tn].start_time = 0; + base_timeouts[tn].fin_time = 0; +} + +/* + * CheckDummyTimeout + * + * Do nothing. Usable for timeout sources that don't need + * extra processing, like authentication_timeout. + * The timeout indicator is set by the signal handler. + */ +static void +DummyTimeoutFunction(void) +{ + return; +} + +/***************************************************************************** + * Public API + *****************************************************************************/ + +/* + * 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 different timeout alarms. + */ +void +enable_timeout(TimeoutName tn, int delayms) +{ + TimestampTz start_time; + TimestampTz fin_time; + int i; + + Assert(!is_timeout_active(tn)); + + if (delayms <= 0) + return; + + start_time = base_timeouts[tn].timeout_start(); + fin_time = TimestampTzPlusMilliseconds(start_time, delayms); + + /* Find out the index where to insert the new timeout. */ + for (i = 0; i < n_timeouts; i++) + { + /* + * The new timeout triggers earlier than a previously added one: + * insert here. + */ + if (fin_time < timeouts[i]->fin_time) + break; + /* + * The new timeout triggers at the same time as the previously added + * one but has greater priority. + */ + if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index) + break; + } + + /* + * Initialize the timeout parameters + */ + InitTimeout(tn, start_time, fin_time); + + insert_timeout(tn, i); + + /* + * If the timeout was inserted in the first position, set the timer. + */ + if (i == 0) + schedule_timeout(start_time); +} + +/* + * Cancel the SIGALRM timer for the specific timeout. + * If 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(TimeoutName tn, bool keep_indicator) +{ + int i; + + /* + * Always disable the timer 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 ->check_timeout(). + */ + if (n_timeouts > 0) + { + struct itimerval timeval; + + MemSet(&timeval, 0, sizeof(struct itimerval)); + if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) + elog(FATAL, "could not disable timer"); + } + + /* Find the timeout and remove from the list. */ + i = find_active_timeout(tn); + if (i < 0) + return; + remove_timeout_index(i); + + /* Do cleanup. */ + DestroyTimeout(tn, keep_indicator); + + /* + * If the first timeout was removed from the list and there is + * at least one active left, reschedule it. + */ + if (i == 0 && n_timeouts > 0) + schedule_timeout(GetCurrentTimestamp()); +} + +/* + * Disable SIGALRM and remove all timeouts from the list and + * reset the 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 timer"); + + n_timeouts = 0; + for (i = 0; i < TIMEOUT_MAX; i++) + DestroyTimeout(i, keep_indicators); +} + +/* + * Return the timeout indicator + */ +bool +get_timeout_indicator(TimeoutName tn) +{ + return base_timeouts[tn].indicator; +} + +/* + * Return the start of the timer for this timeout + */ +TimestampTz +get_timeout_start(TimeoutName tn) +{ + return base_timeouts[tn].start_time; +} + +/* + * Signal handler for SIGALRM + * + * Process the check for the currently active timeout source and + * reschedule the next as needed. To avoid various edge cases, + * we must be careful to do nothing when there is nothing to be done. + */ +void +handle_sig_alarm(SIGNAL_ARGS) +{ + int save_errno = errno; + + /* + * SIGALRM is 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); + + if (n_timeouts > 0) + { + TimestampTz now; + bool reschedule; + + now = GetCurrentTimestamp(); + + /* If our time has not come yet, do nothing. */ + if (now < timeouts[0]->fin_time) + goto out; + + /* + * Set the trigger indicator before calling the checker function. + * Setting if after may have side effects that lead to detecting + * a different event, like "user pressed Ctrl-C" is detected instead + * of the statement timeout having triggered. + */ + timeouts[0]->indicator = true; + + /* Call the timeout checker. */ + timeouts[0]->timeout_func(); + + /* + * Remove the current timeout source and reschedule + * the next if needed. Short circuit disable_timeout(..., true) + * for the timeout source that just triggered. + */ + reschedule = timeouts[0]->resched_next; + remove_timeout_index(0); + + if (reschedule) + schedule_timeout(now); + else + disable_all_timeouts(true); + } + +out: + errno = save_errno; +} diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 9a5438f..804adbf 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -64,6 +64,7 @@ #include "storage/proc.h" #include "storage/procsignal.h" #include "storage/sinval.h" +#include "storage/timeout.h" #include "tcop/fastpath.h" #include "tcop/pquery.h" #include "tcop/tcopprot.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(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_all_timeouts(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(); @@ -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 the timer if running. */ 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 4d4a895..36e61cd 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -41,12 +41,12 @@ #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" #include "storage/sinvaladt.h" #include "storage/smgr.h" +#include "storage/timeout.h" #include "tcop/tcopprot.h" #include "utils/acl.h" #include "utils/fmgroids.h" @@ -205,8 +205,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(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000); /* * Now perform authentication exchange. @@ -216,8 +215,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) { diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 965d325..bc1e41d 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -64,6 +64,7 @@ #include "storage/fd.h" #include "storage/proc.h" #include "storage/predicate.h" +#include "storage/timeout.h" #include "tcop/tcopprot.h" #include "tsearch/ts_cache.h" #include "utils/builtins.h" diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 31f7099..f06d8c7 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -218,13 +218,8 @@ extern PGPROC *PreparedXactProcs; /* configurable options */ -extern int DeadlockTimeout; -extern int StatementTimeout; extern bool log_lock_waits; -extern volatile bool cancel_from_timeout; - - /* * Function Prototypes */ @@ -252,13 +247,4 @@ 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/proctimeout.h b/src/include/storage/proctimeout.h new file mode 100644 index 0000000..5226357 --- /dev/null +++ b/src/include/storage/proctimeout.h @@ -0,0 +1,22 @@ +/*------------------------------------------------------------------------- + * + * proctimeout.h + * declaration of timeout checker functions + * + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/storage/proctimeout.h + * + *------------------------------------------------------------------------- + */ +#ifndef PROCTIMEOUT_H +#define PROCTIMEOUT_H + +extern void CheckDeadLock(void); +extern void StatementTimeoutFunction(void); +extern void StandbyDeadLockFunction(void); +extern void StandbyTimeoutFunction(void); + +#endif /* PROCTIMEOUT_H */