]> granicus.if.org Git - postgresql/commitdiff
Introduce timeout handling framework
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 16 Jul 2012 22:43:21 +0000 (18:43 -0400)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 17 Jul 2012 02:55:33 +0000 (22:55 -0400)
Management of timeouts was getting a little cumbersome; what we
originally had was more than enough back when we were only concerned
about deadlocks and query cancel; however, when we added timeouts for
standby processes, the code got considerably messier.  Since there are
plans to add more complex timeouts, this seems a good time to introduce
a central timeout handling module.

External modules register their timeout handlers during process
initialization, and later enable and disable them as they see fit using
a simple API; timeout.c is in charge of keeping track of which timeouts
are in effect at any time, installing a common SIGALRM signal handler,
and calling setitimer() as appropriate to ensure timely firing of
external handlers.

timeout.c additionally supports pluggable modules to add their own
timeouts, though this capability isn't exercised anywhere yet.

Additionally, as of this commit, walsender processes are aware of
timeouts; we had a preexisting bug there that made those ignore SIGALRM,
thus being subject to unhandled deadlocks, particularly during the
authentication phase.  This has already been fixed in back branches in
commit 0bf8eb2a, which see for more details.

Main author: Zoltán Böszörményi
Some review and cleanup by Álvaro Herrera
Extensive reworking by Tom Lane

13 files changed:
src/backend/postmaster/autovacuum.c
src/backend/postmaster/postmaster.c
src/backend/postmaster/startup.c
src/backend/replication/walsender.c
src/backend/storage/ipc/standby.c
src/backend/storage/lmgr/proc.c
src/backend/tcop/postgres.c
src/backend/utils/init/postinit.c
src/backend/utils/misc/Makefile
src/backend/utils/misc/timeout.c [new file with mode: 0644]
src/include/storage/proc.h
src/include/storage/standby.h
src/include/utils/timeout.h [new file with mode: 0644]

index dade5cc3c051c855de2d2800a4db74f0907e41bb..7c946804a5fddcd3dc905a0f0fcefc788f6ead86 100644 (file)
@@ -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);
index 45f6ac624eb09e6878403cfdc0f1b5e0e05bf389..0be3230c2a5cbf5b697c4ebf6ff98578805e140d 100644 (file)
 #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
  */
index ed75d0958e077905c8bed74b8cde01ef2551d787..ab4d1645f246aa9c56e14bf0374e69dfb4a950c3 100644 (file)
@@ -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();
 
        /*
index d007f5f6fa1865cdd7be4446eecc60c33ebe1430..37a030b5f5e4e1536c44227672be05ffcff88537 100644 (file)
@@ -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
index 995b68aae553f6cbbc1dbfc0a05ddeb4404bfc7c..43f74112733772089a33e39b623572b916fe2d35 100644 (file)
@@ -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
index 21598d3c18635acee2728bd54910732b34904952..0df562b30640f35ebd27e43389c0b99ecf3d87be 100644 (file)
@@ -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;
-}
index f696375cabc5d5e2ddbd3689aaebc13616fddac8..37dfa18c1d0b793d096f662597b4b0a2687a1fd6 100644 (file)
@@ -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 */
 
                /*
index 4d4a895657ed8c67707340efa662c89a25df226f..6a3fc6f693f66304a487032e2099d1660590d836 100644 (file)
@@ -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.
  */
index cd9ba5d1cc235b2e3d8021ce75f9ec1ba5a106c7..08be3bd699d9aa450b08e2eacff2ac084178ba0b 100644 (file)
@@ -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 (file)
index 0000000..668d5f3
--- /dev/null
@@ -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;
+}
index 31f7099a635b12886444fa30c18ac9ebc62e73b1..e10aafe99e43d19d2346e5a43b240ffa339808dc 100644 (file)
@@ -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 */
index ed3b66b35df07553a77a17619e54d52b4a981562..7024fc4f3c2d2d105ab6d5898b5e362a59937148 100644 (file)
@@ -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 (file)
index 0000000..76a7e1a
--- /dev/null
@@ -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 */