Thread: Re: implementing query timeout

Re: implementing query timeout

From
Bruce Momjian
Date:
Here is my first draft of a query timeout SET variable.  It works for a
simple test:

    test=> set query_timeout to '2000';
    SET
    test=> select * from pg_class, pg_type;
    ERROR:  Query was cancelled.
    test=>

I still need to polish it up and do more testing.  Can people comment on
the proper placement of the disable_sig_alarm(true) calls?  Also, the
handling of the alarm is tricky because the deadlock timer uses the
alarm as well and the query_timeout.  (I have not gotten all the cases
correct yet.)

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
Index: doc/src/sgml/runtime.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v
retrieving revision 1.120
diff -c -r1.120 runtime.sgml
*** doc/src/sgml/runtime.sgml    5 Jul 2002 01:17:20 -0000    1.120
--- doc/src/sgml/runtime.sgml    10 Jul 2002 03:58:52 -0000
***************
*** 1585,1590 ****
--- 1585,1600 ----
       </varlistentry>

       <varlistentry>
+       <term><varname>QUERY_TIMEOUT</varname> (<type>integer</type>)</term>
+       <listitem>
+        <para>
+         Aborts any query that takes over the specified number of
+         microseconds.  A value of zero turns off the timer.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
        <term><varname>SHARED_BUFFERS</varname> (<type>integer</type>)</term>
        <listitem>
         <para>
Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v
retrieving revision 1.280
diff -c -r1.280 postmaster.c
*** src/backend/postmaster/postmaster.c    20 Jun 2002 20:29:33 -0000    1.280
--- src/backend/postmaster/postmaster.c    10 Jul 2002 03:58:56 -0000
***************
*** 2105,2111 ****
       * after a time delay, so that a broken client can't hog a connection
       * indefinitely.  PreAuthDelay doesn't count against the time limit.
       */
!     if (!enable_sigalrm_interrupt(AuthenticationTimeout * 1000))
          elog(FATAL, "DoBackend: Unable to set timer for auth timeout");

      /*
--- 2105,2111 ----
       * after a time delay, so that a broken client can't hog a connection
       * indefinitely.  PreAuthDelay doesn't count against the time limit.
       */
!     if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
          elog(FATAL, "DoBackend: Unable to set timer for auth timeout");

      /*
***************
*** 2134,2140 ****
       * Done with authentication.  Disable timeout, and prevent
       * SIGTERM/SIGQUIT again until backend startup is complete.
       */
!     if (!disable_sigalrm_interrupt())
          elog(FATAL, "DoBackend: Unable to disable timer for auth timeout");
      PG_SETMASK(&BlockSig);

--- 2134,2140 ----
       * Done with authentication.  Disable timeout, and prevent
       * SIGTERM/SIGQUIT again until backend startup is complete.
       */
!     if (!disable_sig_alarm(false))
          elog(FATAL, "DoBackend: Unable to disable timer for auth timeout");
      PG_SETMASK(&BlockSig);

Index: src/backend/storage/lmgr/proc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v
retrieving revision 1.121
diff -c -r1.121 proc.c
*** src/backend/storage/lmgr/proc.c    20 Jun 2002 20:29:35 -0000    1.121
--- src/backend/storage/lmgr/proc.c    10 Jul 2002 03:58:56 -0000
***************
*** 52,59 ****
  #include "storage/sinval.h"
  #include "storage/spin.h"

-
  int            DeadlockTimeout = 1000;

  PGPROC       *MyProc = NULL;

--- 52,61 ----
  #include "storage/sinval.h"
  #include "storage/spin.h"

  int            DeadlockTimeout = 1000;
+ int            QueryTimeout = 0;
+ int            RemainingQueryTimeout = 0;
+ bool        alarm_is_query_timeout = false;

  PGPROC       *MyProc = NULL;

***************
*** 319,325 ****
      waitingForLock = false;

      /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
!     disable_sigalrm_interrupt();

      /* Unlink myself from the wait queue, if on it (might not be anymore!) */
      LWLockAcquire(LockMgrLock, LW_EXCLUSIVE);
--- 321,327 ----
      waitingForLock = false;

      /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
!     disable_sig_alarm(false);

      /* Unlink myself from the wait queue, if on it (might not be anymore!) */
      LWLockAcquire(LockMgrLock, LW_EXCLUSIVE);
***************
*** 632,638 ****
       * 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_sigalrm_interrupt(DeadlockTimeout))
          elog(FATAL, "ProcSleep: Unable to set timer for process wakeup");

      /*
--- 634,640 ----
       * 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, "ProcSleep: Unable to set timer for process wakeup");

      /*
***************
*** 654,660 ****
      /*
       * Disable the timer, if it's still running
       */
!     if (!disable_sigalrm_interrupt())
          elog(FATAL, "ProcSleep: Unable to disable timer for process wakeup");

      /*
--- 656,662 ----
      /*
       * Disable the timer, if it's still running
       */
!     if (!disable_sig_alarm(false))
          elog(FATAL, "ProcSleep: Unable to disable timer for process wakeup");

      /*
***************
*** 785,791 ****
   * --------------------
   */
  void
! HandleDeadLock(SIGNAL_ARGS)
  {
      int            save_errno = errno;

--- 787,793 ----
   * --------------------
   */
  void
! HandleDeadLock(void)
  {
      int            save_errno = errno;

***************
*** 921,949 ****
   * Delay is given in milliseconds.    Caller should be sure a SIGALRM
   * signal handler is installed before this is called.
   *
   * Returns TRUE if okay, FALSE on failure.
   */
  bool
! enable_sigalrm_interrupt(int delayms)
  {
  #ifndef __BEOS__
!     struct itimerval timeval,
!                 dummy;

      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, &dummy))
          return false;
  #else
      /* BeOS doesn't have setitimer, but has set_alarm */
-     bigtime_t    time_interval;
-
      time_interval = delayms * 1000;        /* usecs */
!     if (set_alarm(time_interval, B_ONE_SHOT_RELATIVE_ALARM) < 0)
          return false;
  #endif

      return true;
  }

--- 923,1014 ----
   * 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 when the query_timeout
+  * alarm is specified first.
+  *
   * Returns TRUE if okay, FALSE on failure.
   */
  bool
! enable_sig_alarm(int delayms, bool is_query_timeout)
  {
  #ifndef __BEOS__
!     struct itimerval timeval, remaining;
! #else
!     bigtime_t    time_interval, remaining;
! #endif
!     int remainingms;

+     /* Don't set timer if the query timeout scheduled before next alarm. */
+     if (alarm_is_query_timeout &&
+         !is_query_timeout &&
+         RemainingQueryTimeout <= delayms)
+         return true;
+
+ #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))
          return false;
+     if (alarm_is_query_timeout && !is_query_timeout)
+     {
+         remainingms = remaining.it_value.tv_sec * 1000 +
+                       remaining.it_value.tv_usec;
+         /* Query already timed out */
+         if (remainingms == 0)
+         {
+             alarm_is_query_timeout = true;
+             kill(MyProcPid, SIGALRM);
+         }
+         /* Previous alarm < delayms? */
+         if (remainingms < delayms)
+         {
+             alarm_is_query_timeout = true;
+             /* return alarm as though no change was made */
+              if (setitimer(ITIMER_REAL, &remaining, &timeval))
+                 return false;
+             else
+                 return true;
+         }
+         RemainingQueryTimeout = remainingms - delayms;
+     }
+     else if (is_query_timeout)
+         RemainingQueryTimeout = QueryTimeout;
  #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)
          return false;
+     if (alarm_is_query_timeout && !is_query_timeout)
+     {
+         remainingms = remaining / 1000;
+         /* Query already timed out */
+         if (remainingms == 0)
+         {
+             alarm_is_query_timeout = true;
+             kill(MyProcPid, SIGALRM);
+         }
+         /* Previous alarm < delayms? */
+         if (remainingms < delayms)
+         {
+             alarm_is_query_timeout = true;
+             /* return as though no change was made */
+             if ((timeval = set_alarm(remaining, B_ONE_SHOT_RELATIVE_ALARM)) < 0)
+                 return false;
+             else
+                 return true;
+         }
+         RemainingQueryTimeout = remainingms - delayms;
+     }
+     else if (is_query_timeout)
+         RemainingQueryTimeout = QueryTimeout;
  #endif

+     if (is_query_timeout)
+         alarm_is_query_timeout = true;
+     else
+         alarm_is_query_timeout = false;
+
      return true;
  }

***************
*** 953,972 ****
   * Returns TRUE if okay, FALSE on failure.
   */
  bool
! disable_sigalrm_interrupt(void)
  {
  #ifndef __BEOS__
!     struct itimerval timeval,
!                 dummy;

      MemSet(&timeval, 0, sizeof(struct itimerval));
!     if (setitimer(ITIMER_REAL, &timeval, &dummy))
!         return false;
  #else
      /* BeOS doesn't have setitimer, but has set_alarm */
!     if (set_alarm(B_INFINITE_TIMEOUT, B_PERIODIC_ALARM) < 0)
!         return false;
  #endif

      return true;
  }
--- 1018,1087 ----
   * Returns TRUE if okay, FALSE on failure.
   */
  bool
! disable_sig_alarm(bool is_query_timeout)
  {
  #ifndef __BEOS__
!     struct itimerval timeval, dummy;
! #endif

+ #ifndef __BEOS__
      MemSet(&timeval, 0, sizeof(struct itimerval));
!     if (!is_query_timeout && RemainingQueryTimeout)
!     {
!         /* Restore remaining query timeout value */
!         timeval.it_value.tv_sec = RemainingQueryTimeout / 1000;
!         timeval.it_value.tv_usec = (RemainingQueryTimeout % 1000) * 1000;
!         alarm_is_query_timeout = true;
!     }
!     /*
!      *    Optimization: is_query_timeout && RemainingQueryTimeout == 0
!      *  does nothing.  This is for cases where no timeout was set.
!      */
!     if (!is_query_timeout || RemainingQueryTimeout)
!     {
!         if (setitimer(ITIMER_REAL, &timeval, &dummy))
!             return false;
!     }
  #else
      /* BeOS doesn't have setitimer, but has set_alarm */
!     if (!is_query_timeout && RemainingQueryTimeout)
!     {
!         bigtime_t    time_interval = RemainingQueryTimeout * 1000;
!
!         alarm_is_query_timeout = true;
!         if (!is_query_timeout)
!         {
!             if (set_alarm(time_interval, B_ONE_SHOT_RELATIVE_ALARM) < 0)
!                 return false;
!         }
!     }
!     else if (!is_query_timeout || RemainingQueryTimeout)
!     {
!         if (set_alarm(B_INFINITE_TIMEOUT, B_PERIODIC_ALARM) < 0)
!             return false;
!     }
  #endif

+     if (is_query_timeout)
+         RemainingQueryTimeout = 0;
+
      return true;
  }
+
+
+ /*
+  * Call alarm handler.
+  */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+     if (alarm_is_query_timeout)
+     {
+         RemainingQueryTimeout = 0;
+         alarm_is_query_timeout = false;
+         kill(MyProcPid, SIGINT);
+     }
+     else
+         HandleDeadLock();
+ }
+
Index: src/backend/tcop/postgres.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/tcop/postgres.c,v
retrieving revision 1.268
diff -c -r1.268 postgres.c
*** src/backend/tcop/postgres.c    20 Jun 2002 20:29:36 -0000    1.268
--- src/backend/tcop/postgres.c    10 Jul 2002 03:58:58 -0000
***************
*** 78,83 ****
--- 78,85 ----
  /* Note: whereToSendOutput is initialized for the bootstrap/standalone case */
  CommandDest whereToSendOutput = Debug;

+ extern int QueryTimeout;
+
  static bool dontExecute = false;

  /* note: these declarations had better match tcopprot.h */
***************
*** 723,728 ****
--- 725,733 ----
               */
              CHECK_FOR_INTERRUPTS();

+             if (QueryTimeout)
+                 enable_sig_alarm(QueryTimeout, true);
+
              if (querytree->commandType == CMD_UTILITY)
              {
                  /*
***************
*** 791,796 ****
--- 796,803 ----
                      ShowUsage("EXECUTOR STATISTICS");
              }

+             disable_sig_alarm(true);
+
              /*
               * In a query block, we want to increment the command counter
               * between queries so that the effects of early queries are
***************
*** 821,829 ****
                  finish_xact_command();
                  xact_started = false;
              }
!
!         }                        /* end loop over queries generated from a
!                                  * parsetree */

          /*
           * If this is the last parsetree of the query string, close down
--- 828,834 ----
                  finish_xact_command();
                  xact_started = false;
              }
!         } /* end loop over queries generated from a parsetree */

          /*
           * If this is the last parsetree of the query string, close down
***************
*** 1554,1560 ****
      pqsignal(SIGINT, QueryCancelHandler);        /* cancel current query */
      pqsignal(SIGTERM, die);        /* cancel current query and exit */
      pqsignal(SIGQUIT, quickdie);    /* hard crash time */
!     pqsignal(SIGALRM, HandleDeadLock);    /* check for deadlock after
                                           * timeout */

      /*
--- 1559,1565 ----
      pqsignal(SIGINT, QueryCancelHandler);        /* 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 */

      /*
***************
*** 1819,1824 ****
--- 1824,1832 ----
           */
          QueryCancelPending = false;        /* forget any earlier CANCEL
                                           * signal */
+
+         /* Stop any query timer */
+         disable_sig_alarm(true);

          EnableNotifyInterrupt();

Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v
retrieving revision 1.70
diff -c -r1.70 guc.c
*** src/backend/utils/misc/guc.c    16 Jun 2002 00:09:12 -0000    1.70
--- src/backend/utils/misc/guc.c    10 Jul 2002 03:59:00 -0000
***************
*** 51,56 ****
--- 51,57 ----
  extern bool Log_connections;
  extern int    PreAuthDelay;
  extern int    AuthenticationTimeout;
+ extern int    QueryTimeout;
  extern int    CheckPointTimeout;
  extern int    CommitDelay;
  extern int    CommitSiblings;
***************
*** 573,578 ****
--- 574,584 ----
      {
          { "max_expr_depth", PGC_USERSET }, &max_expr_depth,
          DEFAULT_MAX_EXPR_DEPTH, 10, INT_MAX, NULL, NULL
+     },
+
+     {
+         { "query_timeout", PGC_USERSET }, &QueryTimeout,
+         0, 0, INT_MAX, NULL, NULL
      },

      {
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.40
diff -c -r1.40 postgresql.conf.sample
*** src/backend/utils/misc/postgresql.conf.sample    16 Jun 2002 00:09:12 -0000    1.40
--- src/backend/utils/misc/postgresql.conf.sample    10 Jul 2002 03:59:00 -0000
***************
*** 200,202 ****
--- 200,203 ----
  #password_encryption = true
  #sql_inheritance = true
  #transform_null_equals = false
+ #query_timeout = 0                # 0 is disabled
Index: src/bin/psql/tab-complete.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v
retrieving revision 1.50
diff -c -r1.50 tab-complete.c
*** src/bin/psql/tab-complete.c    16 Jun 2002 00:09:12 -0000    1.50
--- src/bin/psql/tab-complete.c    10 Jul 2002 03:59:05 -0000
***************
*** 267,272 ****
--- 267,273 ----

          "default_transaction_isolation",
          "search_path",
+         "query_timeout",

          NULL
      };
Index: src/include/storage/proc.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/storage/proc.h,v
retrieving revision 1.57
diff -c -r1.57 proc.h
*** src/include/storage/proc.h    20 Jun 2002 20:29:52 -0000    1.57
--- src/include/storage/proc.h    10 Jul 2002 03:59:05 -0000
***************
*** 105,117 ****
  extern PGPROC *ProcWakeup(PGPROC *proc, int errType);
  extern void ProcLockWakeup(LOCKMETHODTABLE *lockMethodTable, LOCK *lock);
  extern bool LockWaitCancel(void);
! extern void HandleDeadLock(SIGNAL_ARGS);

  extern void ProcWaitForSignal(void);
  extern void ProcCancelWaitForSignal(void);
  extern void ProcSendSignal(BackendId procId);

! extern bool enable_sigalrm_interrupt(int delayms);
! extern bool disable_sigalrm_interrupt(void);

  #endif   /* PROC_H */
--- 105,118 ----
  extern PGPROC *ProcWakeup(PGPROC *proc, int errType);
  extern void ProcLockWakeup(LOCKMETHODTABLE *lockMethodTable, LOCK *lock);
  extern bool LockWaitCancel(void);
! extern void HandleDeadLock(void);

  extern void ProcWaitForSignal(void);
  extern void ProcCancelWaitForSignal(void);
  extern void ProcSendSignal(BackendId procId);

! extern bool enable_sig_alarm(int delayms, bool is_query_timeout);
! extern bool disable_sig_alarm(bool is_query_timeout);
! extern void handle_sig_alarm(SIGNAL_ARGS);

  #endif   /* PROC_H */

Re: implementing query timeout

From
Peter Eisentraut
Date:
Bruce Momjian writes:

> Here is my first draft of a query timeout SET variable.

Unless it only cancels SELECT statements (which may or may not be a good
idea), please call it statement_timeout.

--
Peter Eisentraut   peter_e@gmx.net


Re: implementing query timeout

From
Bruce Momjian
Date:
Peter Eisentraut wrote:
> Bruce Momjian writes:
>
> > Here is my first draft of a query timeout SET variable.
>
> Unless it only cancels SELECT statements (which may or may not be a good
> idea), please call it statement_timeout.

Woh, you mean only SELECT is a query?  Why is it SQL?  Do we not use
'query' to mean any SQL command?

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: implementing query timeout

From
Bruce Momjian
Date:
Peter Eisentraut wrote:
> Bruce Momjian writes:
>
> > Here is my first draft of a query timeout SET variable.
>
> Unless it only cancels SELECT statements (which may or may not be a good
> idea), please call it statement_timeout.

Do people prefer query_timeout or statement_timeout?  Doesn't matter to
me.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: implementing query timeout

From
Rod Taylor
Date:
On Wed, 2002-07-10 at 18:52, Bruce Momjian wrote:
> Peter Eisentraut wrote:
> > Bruce Momjian writes:
> >
> > > Here is my first draft of a query timeout SET variable.
> >
> > Unless it only cancels SELECT statements (which may or may not be a good
> > idea), please call it statement_timeout.
>
> Do people prefer query_timeout or statement_timeout?  Doesn't matter to
> me.

There is no 'statement' in SQL, that said, 75% of SQL has nothing to do
with queries.

So... I think I vote 'statement'.


Re: implementing query timeout

From
Jan Wieck
Date:
Bruce Momjian wrote:
>
> Peter Eisentraut wrote:
> > Bruce Momjian writes:
> >
> > > Here is my first draft of a query timeout SET variable.
> >
> > Unless it only cancels SELECT statements (which may or may not be a good
> > idea), please call it statement_timeout.
>
> Do people prefer query_timeout or statement_timeout?  Doesn't matter to
> me.

Statements is everything. DDL- and DML-statements. Query is IMHO synonym
for DML-statement. So query_timeout is the right term.


Jan

--

#======================================================================#
# It's easier to get forgiveness for being wrong than for being right. #
# Let's break this rule - forgive me.                                  #
#================================================== JanWieck@Yahoo.com #

Re: implementing query timeout

From
Bruce Momjian
Date:
Jan Wieck wrote:
> Bruce Momjian wrote:
> >
> > Peter Eisentraut wrote:
> > > Bruce Momjian writes:
> > >
> > > > Here is my first draft of a query timeout SET variable.
> > >
> > > Unless it only cancels SELECT statements (which may or may not be a good
> > > idea), please call it statement_timeout.
> >
> > Do people prefer query_timeout or statement_timeout?  Doesn't matter to
> > me.
>
> Statements is everything. DDL- and DML-statements. Query is IMHO synonym
> for DML-statement. So query_timeout is the right term.

But the timeout is for any statement, not just SELECT/UPDATE, etc, so it
sounds like you are voting for 'statement'.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: implementing query timeout

From
"Nicolas Bazin"
Date:
The INFORMIX equivalent is SET LOCK MODE TO WAIT [timeout value in seconds];
If you don't specify any timeout value, you wait until all statements are
completed, If timeout value is set to 0 then you return immediatly if the
tables you query are already locked, ...

This statement is valid for a connection or until another identical
statement is sent.
----- Original Message -----
From: "Bruce Momjian" <pgman@candle.pha.pa.us>
To: "Jan Wieck" <JanWieck@Yahoo.com>
Cc: "Peter Eisentraut" <peter_e@gmx.net>; "Ed Loehr" <ed@LoehrTech.com>;
<dave@fastcrypt.com>; "Matthew Kennedy" <mkennedy@opushealthcare.com>;
"PostgreSQL-patches" <pgsql-patches@postgresql.org>
Sent: Thursday, July 11, 2002 11:34 AM
Subject: Re: [PATCHES] implementing query timeout


> Jan Wieck wrote:
> > Bruce Momjian wrote:
> > >
> > > Peter Eisentraut wrote:
> > > > Bruce Momjian writes:
> > > >
> > > > > Here is my first draft of a query timeout SET variable.
> > > >
> > > > Unless it only cancels SELECT statements (which may or may not be a
good
> > > > idea), please call it statement_timeout.
> > >
> > > Do people prefer query_timeout or statement_timeout?  Doesn't matter
to
> > > me.
> >
> > Statements is everything. DDL- and DML-statements. Query is IMHO synonym
> > for DML-statement. So query_timeout is the right term.
>
> But the timeout is for any statement, not just SELECT/UPDATE, etc, so it
> sounds like you are voting for 'statement'.
>
> --
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 853-3000
>   +  If your life is a hard drive,     |  830 Blythe Avenue
>   +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
>
> ---------------------------(end of broadcast)---------------------------
> TIP 3: if posting/reading through Usenet, please send an appropriate
> subscribe-nomail command to majordomo@postgresql.org so that your
> message can get through to the mailing list cleanly
>
>



Re: implementing query timeout

From
Bruce Momjian
Date:
Nicolas Bazin wrote:
> The INFORMIX equivalent is SET LOCK MODE TO WAIT [timeout value in seconds];
> If you don't specify any timeout value, you wait until all statements are
> completed, If timeout value is set to 0 then you return immediatly if the
> tables you query are already locked, ...
>
> This statement is valid for a connection or until another identical
> statement is sent.

Users want a more general timeout facitily, for example, queries taking
 > 10 minutes.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: implementing query timeout

From
Jan Wieck
Date:
Bruce Momjian wrote:
>
> Jan Wieck wrote:

> > Statements is everything. DDL- and DML-statements. Query is IMHO synonym
> > for DML-statement. So query_timeout is the right term.
>
> But the timeout is for any statement, not just SELECT/UPDATE, etc, so it
> sounds like you are voting for 'statement'.

No, I am voting for 'query'. I don't see the point in allowing a
timeout for utility statements. Why would someone want a timeout
on CREATE INDEX, COPY or VACUUM? Allowing that would IMHO be
calling for more trouble than necessary.


Jan

--

#======================================================================#
# It's easier to get forgiveness for being wrong than for being
right. #
# Let's break this rule - forgive
me.                                  #
#==================================================
JanWieck@Yahoo.com #

Re: implementing query timeout

From
Bruce Momjian
Date:
Jan Wieck wrote:
> Bruce Momjian wrote:
> >
> > Jan Wieck wrote:
>
> > > Statements is everything. DDL- and DML-statements. Query is IMHO synonym
> > > for DML-statement. So query_timeout is the right term.
> >
> > But the timeout is for any statement, not just SELECT/UPDATE, etc, so it
> > sounds like you are voting for 'statement'.
>
> No, I am voting for 'query'. I don't see the point in allowing a
> timeout for utility statements. Why would someone want a timeout
> on CREATE INDEX, COPY or VACUUM? Allowing that would IMHO be
> calling for more trouble than necessary.

Seems pretty arbitrary to time just DML and not DLL.  I can even imagine
this for VACUUM FULL where you don't want it running for a long time.
It is under their control and they can turn it off if they don't want it
for those statements.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: implementing query timeout

From
Bruce Momjian
Date:
Rod Taylor wrote:
> On Wed, 2002-07-10 at 18:52, Bruce Momjian wrote:
> > Peter Eisentraut wrote:
> > > Bruce Momjian writes:
> > >
> > > > Here is my first draft of a query timeout SET variable.
> > >
> > > Unless it only cancels SELECT statements (which may or may not be a good
> > > idea), please call it statement_timeout.
> >
> > Do people prefer query_timeout or statement_timeout?  Doesn't matter to
> > me.
>
> There is no 'statement' in SQL, that said, 75% of SQL has nothing to do
> with queries.
>
> So... I think I vote 'statement'.

OK, changed to 'statement'.  Patch completed, ready for testing.  It
properly handles all the cases I tested, like UPDATE waiting on a lock
and SELECT queries.

I also tested setting statement_timeout to '1' and it allows you to
change it to a different value.  It doesn't cancel the SET before it is
changed.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
Index: doc/src/sgml/runtime.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v
retrieving revision 1.120
diff -c -r1.120 runtime.sgml
*** doc/src/sgml/runtime.sgml    5 Jul 2002 01:17:20 -0000    1.120
--- doc/src/sgml/runtime.sgml    11 Jul 2002 19:08:18 -0000
***************
*** 1585,1590 ****
--- 1585,1600 ----
       </varlistentry>

       <varlistentry>
+       <term><varname>STATEMENT_TIMEOUT</varname> (<type>integer</type>)</term>
+       <listitem>
+        <para>
+         Aborts any statement that takes over the specified number of
+         microseconds.  A value of zero turns off the timer.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
        <term><varname>SHARED_BUFFERS</varname> (<type>integer</type>)</term>
        <listitem>
         <para>
Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v
retrieving revision 1.280
diff -c -r1.280 postmaster.c
*** src/backend/postmaster/postmaster.c    20 Jun 2002 20:29:33 -0000    1.280
--- src/backend/postmaster/postmaster.c    11 Jul 2002 19:08:20 -0000
***************
*** 2105,2111 ****
       * after a time delay, so that a broken client can't hog a connection
       * indefinitely.  PreAuthDelay doesn't count against the time limit.
       */
!     if (!enable_sigalrm_interrupt(AuthenticationTimeout * 1000))
          elog(FATAL, "DoBackend: Unable to set timer for auth timeout");

      /*
--- 2105,2111 ----
       * after a time delay, so that a broken client can't hog a connection
       * indefinitely.  PreAuthDelay doesn't count against the time limit.
       */
!     if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
          elog(FATAL, "DoBackend: Unable to set timer for auth timeout");

      /*
***************
*** 2134,2140 ****
       * Done with authentication.  Disable timeout, and prevent
       * SIGTERM/SIGQUIT again until backend startup is complete.
       */
!     if (!disable_sigalrm_interrupt())
          elog(FATAL, "DoBackend: Unable to disable timer for auth timeout");
      PG_SETMASK(&BlockSig);

--- 2134,2140 ----
       * Done with authentication.  Disable timeout, and prevent
       * SIGTERM/SIGQUIT again until backend startup is complete.
       */
!     if (!disable_sig_alarm(false))
          elog(FATAL, "DoBackend: Unable to disable timer for auth timeout");
      PG_SETMASK(&BlockSig);

Index: src/backend/storage/lmgr/proc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v
retrieving revision 1.121
diff -c -r1.121 proc.c
*** src/backend/storage/lmgr/proc.c    20 Jun 2002 20:29:35 -0000    1.121
--- src/backend/storage/lmgr/proc.c    11 Jul 2002 19:08:21 -0000
***************
*** 52,59 ****
  #include "storage/sinval.h"
  #include "storage/spin.h"

-
  int            DeadlockTimeout = 1000;

  PGPROC       *MyProc = NULL;

--- 52,61 ----
  #include "storage/sinval.h"
  #include "storage/spin.h"

  int            DeadlockTimeout = 1000;
+ int            StatementTimeout = 0;
+ int            RemainingStatementTimeout = 0;
+ bool        alarm_is_statement_timeout = false;

  PGPROC       *MyProc = NULL;

***************
*** 319,325 ****
      waitingForLock = false;

      /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
!     disable_sigalrm_interrupt();

      /* Unlink myself from the wait queue, if on it (might not be anymore!) */
      LWLockAcquire(LockMgrLock, LW_EXCLUSIVE);
--- 321,327 ----
      waitingForLock = false;

      /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
!     disable_sig_alarm(false);

      /* Unlink myself from the wait queue, if on it (might not be anymore!) */
      LWLockAcquire(LockMgrLock, LW_EXCLUSIVE);
***************
*** 632,638 ****
       * 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_sigalrm_interrupt(DeadlockTimeout))
          elog(FATAL, "ProcSleep: Unable to set timer for process wakeup");

      /*
--- 634,640 ----
       * 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, "ProcSleep: Unable to set timer for process wakeup");

      /*
***************
*** 654,660 ****
      /*
       * Disable the timer, if it's still running
       */
!     if (!disable_sigalrm_interrupt())
          elog(FATAL, "ProcSleep: Unable to disable timer for process wakeup");

      /*
--- 656,662 ----
      /*
       * Disable the timer, if it's still running
       */
!     if (!disable_sig_alarm(false))
          elog(FATAL, "ProcSleep: Unable to disable timer for process wakeup");

      /*
***************
*** 785,791 ****
   * --------------------
   */
  void
! HandleDeadLock(SIGNAL_ARGS)
  {
      int            save_errno = errno;

--- 787,793 ----
   * --------------------
   */
  void
! HandleDeadLock(void)
  {
      int            save_errno = errno;

***************
*** 921,949 ****
   * Delay is given in milliseconds.    Caller should be sure a SIGALRM
   * signal handler is installed before this is called.
   *
   * Returns TRUE if okay, FALSE on failure.
   */
  bool
! enable_sigalrm_interrupt(int delayms)
  {
  #ifndef __BEOS__
!     struct itimerval timeval,
!                 dummy;

      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, &dummy))
          return false;
  #else
      /* BeOS doesn't have setitimer, but has set_alarm */
-     bigtime_t    time_interval;
-
      time_interval = delayms * 1000;        /* usecs */
!     if (set_alarm(time_interval, B_ONE_SHOT_RELATIVE_ALARM) < 0)
          return false;
  #endif

      return true;
  }

--- 923,1012 ----
   * 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 when the statement_timeout
+  * alarm is specified first.
+  *
   * Returns TRUE if okay, FALSE on failure.
   */
  bool
! enable_sig_alarm(int delayms, bool is_statement_timeout)
  {
  #ifndef __BEOS__
!     struct itimerval timeval, remaining;
! #else
!     bigtime_t    time_interval, remaining;
! #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;
+
+ #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))
          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)
          return false;
  #endif

+     if (is_statement_timeout)
+         RemainingStatementTimeout = StatementTimeout;
+     else
+     {
+         /* 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;
+         }
+
+         if (RemainingStatementTimeout)
+         {
+             /* 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;
+ #else
+                 remaining = RemainingStatementTimeout * 1000;
+                 if ((timeval = set_alarm(remaining, B_ONE_SHOT_RELATIVE_ALARM)) < 0)
+                     return false;
+                 else
+                     return true;
+ #endif
+             }
+             else
+                 RemainingStatementTimeout -= delayms;
+         }
+     }
+
+     if (is_statement_timeout)
+         alarm_is_statement_timeout = true;
+     else
+         alarm_is_statement_timeout = false;
+
      return true;
  }

***************
*** 953,972 ****
   * Returns TRUE if okay, FALSE on failure.
   */
  bool
! disable_sigalrm_interrupt(void)
  {
  #ifndef __BEOS__
!     struct itimerval timeval,
!                 dummy;

      MemSet(&timeval, 0, sizeof(struct itimerval));
!     if (setitimer(ITIMER_REAL, &timeval, &dummy))
!         return false;
  #else
      /* BeOS doesn't have setitimer, but has set_alarm */
!     if (set_alarm(B_INFINITE_TIMEOUT, B_PERIODIC_ALARM) < 0)
!         return false;
  #endif

      return true;
  }
--- 1016,1087 ----
   * Returns TRUE if okay, FALSE on failure.
   */
  bool
! disable_sig_alarm(bool is_statement_timeout)
  {
  #ifndef __BEOS__
!     struct itimerval timeval, dummy;

      MemSet(&timeval, 0, sizeof(struct itimerval));
!     if (!is_statement_timeout && RemainingStatementTimeout)
!     {
!         /* Restore remaining statement timeout value */
!         alarm_is_statement_timeout = true;
!         timeval.it_value.tv_sec = RemainingStatementTimeout / 1000;
!         timeval.it_value.tv_usec = (RemainingStatementTimeout % 1000) * 1000;
!     }
!     /*
!      *    Optimization: is_statement_timeout && RemainingStatementTimeout == 0
!      *  does nothing.  This is for cases where no timeout was set.
!      */
!     if (!is_statement_timeout || RemainingStatementTimeout)
!     {
!         if (setitimer(ITIMER_REAL, &timeval, &dummy))
!             return false;
!     }
  #else
      /* BeOS doesn't have setitimer, but has set_alarm */
!     if (!is_statement_timeout && RemainingStatementTimeout)
!     {
!         bigtime_t    time_interval = RemainingStatementTimeout * 1000;
!
!         alarm_is_statement_timeout = true;
!         if (!is_statement_timeout)
!         {
!             if (set_alarm(time_interval, B_ONE_SHOT_RELATIVE_ALARM) < 0)
!                 return false;
!         }
!     }
!     else if (!is_statement_timeout || RemainingStatementTimeout)
!     {
!         if (set_alarm(B_INFINITE_TIMEOUT, B_PERIODIC_ALARM) < 0)
!             return false;
!     }
  #endif

+     if (is_statement_timeout)
+         RemainingStatementTimeout = 0;
+
      return true;
  }
+
+
+ /*
+  * Call alarm handler.
+  */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+     if (alarm_is_statement_timeout)
+     {
+         RemainingStatementTimeout = 0;
+         alarm_is_statement_timeout = false;
+         kill(MyProcPid, SIGINT);
+     }
+     else
+     {
+         HandleDeadLock();
+         /* Reactivate any statement_timeout alarm. */
+         disable_sig_alarm(false);
+     }
+ }
+
Index: src/backend/tcop/postgres.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/tcop/postgres.c,v
retrieving revision 1.268
diff -c -r1.268 postgres.c
*** src/backend/tcop/postgres.c    20 Jun 2002 20:29:36 -0000    1.268
--- src/backend/tcop/postgres.c    11 Jul 2002 19:08:24 -0000
***************
*** 78,83 ****
--- 78,85 ----
  /* 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 */
***************
*** 717,722 ****
--- 719,727 ----
                  xact_started = true;
              }

+             if (StatementTimeout)
+                 enable_sig_alarm(StatementTimeout, true);
+
              /*
               * If we got a cancel signal in analysis or prior command,
               * quit
***************
*** 791,796 ****
--- 796,803 ----
                      ShowUsage("EXECUTOR STATISTICS");
              }

+             disable_sig_alarm(true);
+
              /*
               * In a query block, we want to increment the command counter
               * between queries so that the effects of early queries are
***************
*** 821,829 ****
                  finish_xact_command();
                  xact_started = false;
              }
!
!         }                        /* end loop over queries generated from a
!                                  * parsetree */

          /*
           * If this is the last parsetree of the query string, close down
--- 828,834 ----
                  finish_xact_command();
                  xact_started = false;
              }
!         } /* end loop over queries generated from a parsetree */

          /*
           * If this is the last parsetree of the query string, close down
***************
*** 996,1002 ****
   * at soonest convenient time
   */
  static void
! QueryCancelHandler(SIGNAL_ARGS)
  {
      int            save_errno = errno;

--- 1001,1007 ----
   * at soonest convenient time
   */
  static void
! StatementCancelHandler(SIGNAL_ARGS)
  {
      int            save_errno = errno;

***************
*** 1551,1560 ****
       */

      pqsignal(SIGHUP, SigHupHandler);    /* set flag to read config file */
!     pqsignal(SIGINT, QueryCancelHandler);        /* cancel current query */
      pqsignal(SIGTERM, die);        /* cancel current query and exit */
      pqsignal(SIGQUIT, quickdie);    /* hard crash time */
!     pqsignal(SIGALRM, HandleDeadLock);    /* check for deadlock after
                                           * timeout */

      /*
--- 1556,1565 ----
       */

      pqsignal(SIGHUP, SigHupHandler);    /* set flag to read config file */
!     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 */

      /*
***************
*** 1819,1824 ****
--- 1824,1832 ----
           */
          QueryCancelPending = false;        /* forget any earlier CANCEL
                                           * signal */
+
+         /* Stop any statement timer */
+         disable_sig_alarm(true);

          EnableNotifyInterrupt();

Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v
retrieving revision 1.70
diff -c -r1.70 guc.c
*** src/backend/utils/misc/guc.c    16 Jun 2002 00:09:12 -0000    1.70
--- src/backend/utils/misc/guc.c    11 Jul 2002 19:08:25 -0000
***************
*** 51,56 ****
--- 51,57 ----
  extern bool Log_connections;
  extern int    PreAuthDelay;
  extern int    AuthenticationTimeout;
+ extern int    StatementTimeout;
  extern int    CheckPointTimeout;
  extern int    CommitDelay;
  extern int    CommitSiblings;
***************
*** 573,578 ****
--- 574,584 ----
      {
          { "max_expr_depth", PGC_USERSET }, &max_expr_depth,
          DEFAULT_MAX_EXPR_DEPTH, 10, INT_MAX, NULL, NULL
+     },
+
+     {
+         { "statement_timeout", PGC_USERSET }, &StatementTimeout,
+         0, 0, INT_MAX, NULL, NULL
      },

      {
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.40
diff -c -r1.40 postgresql.conf.sample
*** src/backend/utils/misc/postgresql.conf.sample    16 Jun 2002 00:09:12 -0000    1.40
--- src/backend/utils/misc/postgresql.conf.sample    11 Jul 2002 19:08:25 -0000
***************
*** 200,202 ****
--- 200,203 ----
  #password_encryption = true
  #sql_inheritance = true
  #transform_null_equals = false
+ #statement_timeout = 0                # 0 is disabled
Index: src/bin/psql/tab-complete.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v
retrieving revision 1.50
diff -c -r1.50 tab-complete.c
*** src/bin/psql/tab-complete.c    16 Jun 2002 00:09:12 -0000    1.50
--- src/bin/psql/tab-complete.c    11 Jul 2002 19:08:31 -0000
***************
*** 267,272 ****
--- 267,273 ----

          "default_transaction_isolation",
          "search_path",
+         "statement_timeout",

          NULL
      };
Index: src/include/storage/proc.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/storage/proc.h,v
retrieving revision 1.57
diff -c -r1.57 proc.h
*** src/include/storage/proc.h    20 Jun 2002 20:29:52 -0000    1.57
--- src/include/storage/proc.h    11 Jul 2002 19:08:31 -0000
***************
*** 105,117 ****
  extern PGPROC *ProcWakeup(PGPROC *proc, int errType);
  extern void ProcLockWakeup(LOCKMETHODTABLE *lockMethodTable, LOCK *lock);
  extern bool LockWaitCancel(void);
! extern void HandleDeadLock(SIGNAL_ARGS);

  extern void ProcWaitForSignal(void);
  extern void ProcCancelWaitForSignal(void);
  extern void ProcSendSignal(BackendId procId);

! extern bool enable_sigalrm_interrupt(int delayms);
! extern bool disable_sigalrm_interrupt(void);

  #endif   /* PROC_H */
--- 105,118 ----
  extern PGPROC *ProcWakeup(PGPROC *proc, int errType);
  extern void ProcLockWakeup(LOCKMETHODTABLE *lockMethodTable, LOCK *lock);
  extern bool LockWaitCancel(void);
! extern void HandleDeadLock(void);

  extern void ProcWaitForSignal(void);
  extern void ProcCancelWaitForSignal(void);
  extern void ProcSendSignal(BackendId procId);

! 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);

  #endif   /* PROC_H */