*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v 1.126 2002/09/25 20:31:40 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v 1.127 2002/10/31 21:34:16 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "storage/sinval.h"
#include "storage/spin.h"
+/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
-int RemainingStatementTimeout = 0;
-bool alarm_is_statement_timeout = false;
+/* Pointer to this process's PGPROC struct, if any */
PGPROC *MyProc = NULL;
/*
static bool waitingForLock = false;
static bool waitingForSignal = false;
+/* Mark these volatile because they can be changed by signal handler */
+static volatile bool statement_timeout_active = false;
+static volatile bool deadlock_timeout_active = false;
+/* statement_fin_time is valid only if statement_timeout_active is true */
+static struct timeval statement_fin_time;
+
+
static void ProcKill(void);
static void DummyProcKill(void);
+static bool CheckStatementTimeout(void);
/*
* and signal an error to ProcSleep.
* --------------------
*/
-void
+static void
CheckDeadLock(void)
{
- int save_errno = errno;
-
/*
- * Acquire locktable lock. Note that the SIGALRM interrupt had better
- * not be enabled anywhere that this process itself holds the
+ * Acquire locktable lock. Note that the deadlock check interrupt had
+ * better not be enabled anywhere that this process itself holds the
* locktable lock, else this will wait forever. Also note that
* LWLockAcquire creates a critical section, so that this routine
* cannot be interrupted by cancel/die interrupts.
* This is quicker than checking our semaphore's state, since no
* kernel call is needed, and it is safe because we hold the locktable
* lock.
- *
*/
if (MyProc->links.prev == INVALID_OFFSET ||
MyProc->links.next == INVALID_OFFSET)
{
LWLockRelease(LockMgrLock);
- errno = save_errno;
return;
}
{
/* No deadlock, so keep waiting */
LWLockRelease(LockMgrLock);
- errno = save_errno;
return;
}
/*
* Set MyProc->errType to STATUS_ERROR so that ProcSleep will report
- * an error after we return from this signal handler.
+ * an error after we return from the signal handler.
*/
MyProc->errType = STATUS_ERROR;
* RemoveFromWaitQueue took care of waking up any such processes.
*/
LWLockRelease(LockMgrLock);
- errno = save_errno;
}
* Delay is given in milliseconds. Caller should be sure a SIGALRM
* signal handler is installed before this is called.
*
- * This code properly handles multiple alarms when the statement_timeout
- * alarm is specified first.
+ * 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)
{
+ struct timeval fin_time;
#ifndef __BEOS__
- struct itimerval timeval,
- remaining;
-
+ struct itimerval timeval;
#else
- bigtime_t time_interval,
- remaining;
+ bigtime_t time_interval;
#endif
- /*
- * Don't set timer if the statement timeout scheduled before next
- * alarm.
- */
- if (alarm_is_statement_timeout &&
- !is_statement_timeout &&
- RemainingStatementTimeout <= delayms)
- return true;
+ /* Compute target timeout time if we will need it */
+ if (is_statement_timeout || statement_timeout_active)
+ {
+ gettimeofday(&fin_time, NULL);
+ fin_time.tv_sec += delayms / 1000;
+ fin_time.tv_usec += (delayms % 1000) * 1000;
+ if (fin_time.tv_usec >= 1000000)
+ {
+ fin_time.tv_sec++;
+ fin_time.tv_usec -= 1000000;
+ }
+ }
+
+ if (is_statement_timeout)
+ {
+ /* Begin statement-level timeout */
+ Assert(!deadlock_timeout_active);
+ statement_fin_time = fin_time;
+ 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.
+ */
+ deadlock_timeout_active = true;
+ if (fin_time.tv_sec > statement_fin_time.tv_sec ||
+ (fin_time.tv_sec == statement_fin_time.tv_sec &&
+ fin_time.tv_usec >= statement_fin_time.tv_usec))
+ return true;
+ }
+ else
+ {
+ /* Begin deadlock timeout with no statement-level timeout */
+ deadlock_timeout_active = true;
+ }
+ /* If we reach here, okay to set the timer interrupt */
#ifndef __BEOS__
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, &remaining))
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
return false;
#else
/* BeOS doesn't have setitimer, but has set_alarm */
time_interval = delayms * 1000; /* usecs */
- if ((remaining = set_alarm(time_interval, B_ONE_SHOT_RELATIVE_ALARM)) < 0)
+ if (set_alarm(time_interval, B_ONE_SHOT_RELATIVE_ALARM) < 0)
return false;
#endif
- if (is_statement_timeout)
- RemainingStatementTimeout = StatementTimeout;
- else
+ 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)
{
- /* Switching to non-statement-timeout alarm, get remaining time */
- if (alarm_is_statement_timeout)
- {
#ifndef __BEOS__
- /* We lose precision here because we convert to milliseconds */
- RemainingStatementTimeout = remaining.it_value.tv_sec * 1000 +
- remaining.it_value.tv_usec / 1000;
-#else
- RemainingStatementTimeout = remaining / 1000;
-#endif
- /* Rounding could cause a zero */
- if (RemainingStatementTimeout == 0)
- RemainingStatementTimeout = 1;
- }
+ struct itimerval timeval;
- if (RemainingStatementTimeout)
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
{
- /* Remaining timeout alarm < delayms? */
- if (RemainingStatementTimeout <= delayms)
- {
- /* reinstall statement timeout alarm */
- alarm_is_statement_timeout = true;
-#ifndef __BEOS__
- remaining.it_value.tv_sec = RemainingStatementTimeout / 1000;
- remaining.it_value.tv_usec = (RemainingStatementTimeout % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &remaining, &timeval))
- return false;
- else
- return true;
+ statement_timeout_active = deadlock_timeout_active = false;
+ return false;
+ }
#else
- remaining = RemainingStatementTimeout * 1000;
- if ((timeval = set_alarm(remaining, B_ONE_SHOT_RELATIVE_ALARM)) < 0)
- return false;
- else
- return true;
-#endif
- }
- else
- RemainingStatementTimeout -= delayms;
+ /* BeOS doesn't have setitimer, but has set_alarm */
+ if (set_alarm(B_INFINITE_TIMEOUT, B_PERIODIC_ALARM) < 0)
+ {
+ statement_timeout_active = deadlock_timeout_active = false;
+ return false;
}
+#endif
}
+ /* Always cancel deadlock timeout, in case this is error cleanup */
+ deadlock_timeout_active = false;
+
+ /* Cancel or reschedule statement timeout */
if (is_statement_timeout)
- alarm_is_statement_timeout = true;
- else
- alarm_is_statement_timeout = false;
+ statement_timeout_active = false;
+ else if (statement_timeout_active)
+ {
+ if (!CheckStatementTimeout())
+ return false;
+ }
return true;
}
+
/*
- * Cancel the SIGALRM timer.
+ * 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.
*
- * This is also called if the timer has fired to reschedule
- * the statement_timeout timer.
- *
- * Returns TRUE if okay, FALSE on failure.
+ * Returns true if okay, false if failed to set the interrupt.
*/
-bool
-disable_sig_alarm(bool is_statement_timeout)
+static bool
+CheckStatementTimeout(void)
{
-#ifndef __BEOS__
- struct itimerval timeval,
- remaining;
+ struct timeval now;
- MemSet(&timeval, 0, sizeof(struct itimerval));
-#else
- bigtime_t time_interval = 0;
-#endif
+ if (!statement_timeout_active)
+ return true; /* do nothing if not active */
+
+ gettimeofday(&now, NULL);
- if (!is_statement_timeout && RemainingStatementTimeout)
+ if (now.tv_sec > statement_fin_time.tv_sec ||
+ (now.tv_sec == statement_fin_time.tv_sec &&
+ now.tv_usec >= statement_fin_time.tv_usec))
{
+ /* Time to die */
+ statement_timeout_active = false;
+ kill(MyProcPid, SIGINT);
+ }
+ else
+ {
+ /* Not time yet, so (re)schedule the interrupt */
#ifndef __BEOS__
- /* turn off timer and get remaining time, if any */
- if (setitimer(ITIMER_REAL, &timeval, &remaining))
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = statement_fin_time.tv_sec - now.tv_sec;
+ timeval.it_value.tv_usec = statement_fin_time.tv_usec - now.tv_usec;
+ if (timeval.it_value.tv_usec < 0)
+ {
+ timeval.it_value.tv_sec--;
+ timeval.it_value.tv_usec += 1000000;
+ }
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
return false;
- /* Add remaining time back because the timer didn't complete */
- RemainingStatementTimeout += remaining.it_value.tv_sec * 1000 +
- remaining.it_value.tv_usec / 1000;
- /* Prepare to set timer */
- timeval.it_value.tv_sec = RemainingStatementTimeout / 1000;
- timeval.it_value.tv_usec = (RemainingStatementTimeout % 1000) * 1000;
#else
/* BeOS doesn't have setitimer, but has set_alarm */
- if ((time_interval = set_alarm(B_INFINITE_TIMEOUT, B_PERIODIC_ALARM)) < 0)
- return false;
- RemainingStatementTimeout += time_interval / 1000;
- time_interval = RemainingStatementTimeout * 1000;
-#endif
- /* Restore remaining statement timeout value */
- alarm_is_statement_timeout = true;
- }
+ bigtime_t time_interval;
- /*
- * Optimization: is_statement_timeout && RemainingStatementTimeout ==
- * 0 does nothing. This is for cases where no timeout was set.
- */
- if (!is_statement_timeout || RemainingStatementTimeout)
- {
-#ifndef __BEOS__
- if (setitimer(ITIMER_REAL, &timeval, &remaining))
+ time_interval =
+ (statement_fin_time.tv_sec - now.tv_sec) * 1000000 +
+ (statement_fin_time.tv_usec - now.tv_usec);
+ if (set_alarm(time_interval, B_ONE_SHOT_RELATIVE_ALARM) < 0)
return false;
-#else
- if (time_interval)
- {
- if (set_alarm(time_interval, B_ONE_SHOT_RELATIVE_ALARM) < 0)
- return false;
- }
- else
- {
- if (set_alarm(B_INFINITE_TIMEOUT, B_PERIODIC_ALARM) < 0)
- return false;
- }
#endif
}
- if (is_statement_timeout)
- RemainingStatementTimeout = 0;
-
return true;
}
/*
- * Call alarm handler, either StatementCancel or Deadlock checker.
+ * Signal handler for SIGALRM
+ *
+ * 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)
{
- if (alarm_is_statement_timeout)
- {
- RemainingStatementTimeout = 0;
- alarm_is_statement_timeout = false;
- kill(MyProcPid, SIGINT);
- }
- else
+ int save_errno = errno;
+
+ if (deadlock_timeout_active)
{
+ deadlock_timeout_active = false;
CheckDeadLock();
- /* Reactivate any statement_timeout alarm. */
- disable_sig_alarm(false);
}
+
+ if (statement_timeout_active)
+ (void) CheckStatementTimeout();
+
+ errno = save_errno;
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.306 2002/10/24 23:19:13 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.307 2002/10/31 21:34:16 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
/* Note: whereToSendOutput is initialized for the bootstrap/standalone case */
CommandDest whereToSendOutput = Debug;
-extern int StatementTimeout;
-
static bool dontExecute = false;
/* note: these declarations had better match tcopprot.h */
start_xact_command();
xact_started = true;
- if (StatementTimeout)
- enable_sig_alarm(StatementTimeout, true);
-
/*
* parse_context *must* be different from the execution memory
* context, else the context reset at the bottom of the loop will
EndCommand(commandTag, dest);
} /* end loop over parsetrees */
- disable_sig_alarm(true);
-
/*
* Close down transaction statement, if one is open. (Note that this
* will only happen if the querystring was empty.)
{
elog(DEBUG1, "StartTransactionCommand");
StartTransactionCommand(false);
+
+ /* Set statement timeout running, if any */
+ if (StatementTimeout > 0)
+ enable_sig_alarm(StatementTimeout, true);
}
static void
/* Invoke IMMEDIATE constraint triggers */
DeferredTriggerEndQuery();
+ /* Cancel any active statement timeout before committing */
+ disable_sig_alarm(true);
+
/* Now commit the command */
elog(DEBUG1, "CommitTransactionCommand");
/* until we are done getting ready for it */
InterruptHoldoffCount++;
DisableNotifyInterrupt();
- /* Make sure HandleDeadLock won't run while shutting down... */
+ /* Make sure CheckDeadLock won't run while shutting down... */
LockWaitCancel();
InterruptHoldoffCount--;
ProcessInterrupts();
pqsignal(SIGINT, StatementCancelHandler); /* cancel current query */
pqsignal(SIGTERM, die); /* cancel current query and exit */
pqsignal(SIGQUIT, quickdie); /* hard crash time */
- pqsignal(SIGALRM, handle_sig_alarm); /* check for deadlock
- * after timeout */
+ pqsignal(SIGALRM, handle_sig_alarm); /* timeout conditions */
/*
* Ignore failure to write to frontend. Note: if frontend closes
if (!IsUnderPostmaster)
{
puts("\nPOSTGRES backend interactive interface ");
- puts("$Revision: 1.306 $ $Date: 2002/10/24 23:19:13 $\n");
+ puts("$Revision: 1.307 $ $Date: 2002/10/31 21:34:16 $\n");
}
/*
QueryCancelPending = false;
InterruptHoldoffCount = 1;
CritSectionCount = 0; /* should be unnecessary, but... */
+ disable_sig_alarm(true);
+ QueryCancelPending = false; /* again in case timeout occurred */
DisableNotifyInterrupt();
debug_query_string = NULL;
QueryCancelPending = false; /* forget any earlier CANCEL
* signal */
- /* Stop any statement timer */
- disable_sig_alarm(true);
-
EnableNotifyInterrupt();
/* Allow "die" interrupt to be processed while waiting */