Add idle_session_timeout.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 6 Jan 2021 23:28:42 +0000 (18:28 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 6 Jan 2021 23:28:52 +0000 (18:28 -0500)
This GUC variable works much like idle_in_transaction_session_timeout,
in that it kills sessions that have waited too long for a new client
query.  But it applies when we're not in a transaction, rather than
when we are.

Li Japin, reviewed by David Johnston and Hayato Kuroda, some
fixes by me

Discussion: https://postgr.es/m/763A0689-F189-459E-946F-F0EC4458980B@hotmail.com

doc/src/sgml/config.sgml
src/backend/storage/lmgr/proc.c
src/backend/tcop/postgres.c
src/backend/utils/errcodes.txt
src/backend/utils/init/globals.c
src/backend/utils/init/postinit.c
src/backend/utils/misc/guc.c
src/backend/utils/misc/postgresql.conf.sample
src/include/miscadmin.h
src/include/storage/proc.h
src/include/utils/timeout.h

index 425f57901d730d0f58cc429b78cf0020385306af..15b94c96c084ddc1bd79308a9ee8c946fb54846b 100644 (file)
@@ -8310,15 +8310,52 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </term>
       <listitem>
        <para>
-       Terminate any session with an open transaction that has been idle for
-       longer than the specified amount of time. This allows any
-       locks held by that session to be released and the connection slot to be reused;
-       it also allows tuples visible only to this transaction to be vacuumed.  See
-       <xref linkend="routine-vacuuming"/> for more details about this.
+        Terminate any session that has been idle (that is, waiting for a
+        client query) within an open transaction for longer than the
+        specified amount of time.
+        If this value is specified without units, it is taken as milliseconds.
+        A value of zero (the default) disables the timeout.
+       </para>
+
+       <para>
+        This option can be used to ensure that idle sessions do not hold
+        locks for an unreasonable amount of time.  Even when no significant
+        locks are held, an open transaction prevents vacuuming away
+        recently-dead tuples that may be visible only to this transaction;
+        so remaining idle for a long time can contribute to table bloat.
+        See <xref linkend="routine-vacuuming"/> for more details.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="guc-idle-session-timeout" xreflabel="idle_session_timeout">
+      <term><varname>idle_session_timeout</varname> (<type>integer</type>)
+      <indexterm>
+       <primary><varname>idle_session_timeout</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Terminate any session that has been idle (that is, waiting for a
+        client query), but not within an open transaction, for longer than
+        the specified amount of time.
+        If this value is specified without units, it is taken as milliseconds.
+        A value of zero (the default) disables the timeout.
        </para>
+
+       <para>
+        Unlike the case with an open transaction, an idle session without a
+        transaction imposes no large costs on the server, so there is less
+        need to enable this timeout
+        than <varname>idle_in_transaction_session_timeout</varname>.
+       </para>
+
        <para>
-       If this value is specified without units, it is taken as milliseconds.
-       A value of zero (the default) disables the timeout.
+        Be wary of enforcing this timeout on connections made through
+        connection-pooling software or other middleware, as such a layer
+        may not react well to unexpected connection closure.  It may be
+        helpful to enable this timeout only for interactive sessions,
+        perhaps by applying it only to particular users.
        </para>
       </listitem>
      </varlistentry>
index 9b6aa2fe0de05be727e2e434b508f47b98e32773..0366a7cc004e4198e58164e7fb56b8271b1881a5 100644 (file)
@@ -61,6 +61,7 @@ int           DeadlockTimeout = 1000;
 int            StatementTimeout = 0;
 int            LockTimeout = 0;
 int            IdleInTransactionSessionTimeout = 0;
+int            IdleSessionTimeout = 0;
 bool       log_lock_waits = false;
 
 /* Pointer to this process's PGPROC struct, if any */
index 9d98c028a2ddd57987b20d5acea1a224c304c67a..2b53ebf97dc48d5210eae7021f2687a489b158f5 100644 (file)
@@ -3242,14 +3242,28 @@ ProcessInterrupts(void)
 
    if (IdleInTransactionSessionTimeoutPending)
    {
-       /* Has the timeout setting changed since last we looked? */
+       /*
+        * If the GUC has been reset to zero, ignore the signal.  This is
+        * important because the GUC update itself won't disable any pending
+        * interrupt.
+        */
        if (IdleInTransactionSessionTimeout > 0)
            ereport(FATAL,
                    (errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
                     errmsg("terminating connection due to idle-in-transaction timeout")));
        else
            IdleInTransactionSessionTimeoutPending = false;
+   }
 
+   if (IdleSessionTimeoutPending)
+   {
+       /* As above, ignore the signal if the GUC has been reset to zero. */
+       if (IdleSessionTimeout > 0)
+           ereport(FATAL,
+                   (errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
+                    errmsg("terminating connection due to idle-session timeout")));
+       else
+           IdleSessionTimeoutPending = false;
    }
 
    if (ProcSignalBarrierPending)
@@ -3826,7 +3840,8 @@ PostgresMain(int argc, char *argv[],
    StringInfoData input_message;
    sigjmp_buf  local_sigjmp_buf;
    volatile bool send_ready_for_query = true;
-   bool        disable_idle_in_transaction_timeout = false;
+   bool        idle_in_transaction_timeout_enabled = false;
+   bool        idle_session_timeout_enabled = false;
 
    /* Initialize startup process environment if necessary. */
    if (!IsUnderPostmaster)
@@ -4228,6 +4243,8 @@ PostgresMain(int argc, char *argv[],
         * processing of batched messages, and because we don't want to report
         * uncommitted updates (that confuses autovacuum).  The notification
         * processor wants a call too, if we are not in a transaction block.
+        *
+        * Also, if an idle timeout is enabled, start the timer for that.
         */
        if (send_ready_for_query)
        {
@@ -4239,7 +4256,7 @@ PostgresMain(int argc, char *argv[],
                /* Start the idle-in-transaction timer */
                if (IdleInTransactionSessionTimeout > 0)
                {
-                   disable_idle_in_transaction_timeout = true;
+                   idle_in_transaction_timeout_enabled = true;
                    enable_timeout_after(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
                                         IdleInTransactionSessionTimeout);
                }
@@ -4252,7 +4269,7 @@ PostgresMain(int argc, char *argv[],
                /* Start the idle-in-transaction timer */
                if (IdleInTransactionSessionTimeout > 0)
                {
-                   disable_idle_in_transaction_timeout = true;
+                   idle_in_transaction_timeout_enabled = true;
                    enable_timeout_after(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
                                         IdleInTransactionSessionTimeout);
                }
@@ -4275,6 +4292,14 @@ PostgresMain(int argc, char *argv[],
 
                set_ps_display("idle");
                pgstat_report_activity(STATE_IDLE, NULL);
+
+               /* Start the idle-session timer */
+               if (IdleSessionTimeout > 0)
+               {
+                   idle_session_timeout_enabled = true;
+                   enable_timeout_after(IDLE_SESSION_TIMEOUT,
+                                        IdleSessionTimeout);
+               }
            }
 
            /* Report any recently-changed GUC options */
@@ -4310,12 +4335,21 @@ PostgresMain(int argc, char *argv[],
        DoingCommandRead = false;
 
        /*
-        * (5) turn off the idle-in-transaction timeout
+        * (5) turn off the idle-in-transaction and idle-session timeouts, if
+        * active.
+        *
+        * At most one of these two will be active, so there's no need to
+        * worry about combining the timeout.c calls into one.
         */
-       if (disable_idle_in_transaction_timeout)
+       if (idle_in_transaction_timeout_enabled)
        {
            disable_timeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT, false);
-           disable_idle_in_transaction_timeout = false;
+           idle_in_transaction_timeout_enabled = false;
+       }
+       if (idle_session_timeout_enabled)
+       {
+           disable_timeout(IDLE_SESSION_TIMEOUT, false);
+           idle_session_timeout_enabled = false;
        }
 
        /*
index 64ca2deec9995d7da77fb66f79d8b68e22d7c0d7..1d5a78e73d6b9535ee5a038db6cac851d6bae476 100644 (file)
@@ -109,6 +109,7 @@ Section: Class 08 - Connection Exception
 08004    E    ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION      sqlserver_rejected_establishment_of_sqlconnection
 08007    E    ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN                         transaction_resolution_unknown
 08P01    E    ERRCODE_PROTOCOL_VIOLATION                                     protocol_violation
+08P02    E    ERRCODE_IDLE_SESSION_TIMEOUT                                   idle_session_timeout
 
 Section: Class 09 - Triggered Action Exception
 
index 3d5d6cc033c66e3d521e4f5ebbe39c9e91e2d89d..ea28769d6a3966a496f139c66816ea202b798c34 100644 (file)
@@ -32,6 +32,7 @@ volatile sig_atomic_t QueryCancelPending = false;
 volatile sig_atomic_t ProcDiePending = false;
 volatile sig_atomic_t ClientConnectionLost = false;
 volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
+volatile sig_atomic_t IdleSessionTimeoutPending = false;
 volatile sig_atomic_t ProcSignalBarrierPending = false;
 volatile uint32 InterruptHoldoffCount = 0;
 volatile uint32 QueryCancelHoldoffCount = 0;
index 59b3f4b135988d629e55edd31736fb31c8cba7da..e5965bc517dd93a2110b5deec8d2f44d3e56b5dd 100644 (file)
@@ -72,6 +72,7 @@ static void ShutdownPostgres(int code, Datum arg);
 static void StatementTimeoutHandler(void);
 static void LockTimeoutHandler(void);
 static void IdleInTransactionSessionTimeoutHandler(void);
+static void IdleSessionTimeoutHandler(void);
 static bool ThereIsAtLeastOneRole(void);
 static void process_startup_options(Port *port, bool am_superuser);
 static void process_settings(Oid databaseid, Oid roleid);
@@ -619,6 +620,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
        RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
        RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
                        IdleInTransactionSessionTimeoutHandler);
+       RegisterTimeout(IDLE_SESSION_TIMEOUT, IdleSessionTimeoutHandler);
    }
 
    /*
@@ -1233,6 +1235,14 @@ IdleInTransactionSessionTimeoutHandler(void)
    SetLatch(MyLatch);
 }
 
+static void
+IdleSessionTimeoutHandler(void)
+{
+   IdleSessionTimeoutPending = true;
+   InterruptPending = true;
+   SetLatch(MyLatch);
+}
+
 /*
  * Returns true if at least one role is defined in this database cluster.
  */
index 1ccf7593eea3350c5f0e13a1bfc9e43759038480..daf9c127cde679917bd1ad27143230edb4f02a8d 100644 (file)
@@ -2509,7 +2509,7 @@ static struct config_int ConfigureNamesInt[] =
 
    {
        {"idle_in_transaction_session_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
-           gettext_noop("Sets the maximum allowed duration of any idling transaction."),
+           gettext_noop("Sets the maximum allowed idle time between queries, when in a transaction."),
            gettext_noop("A value of 0 turns off the timeout."),
            GUC_UNIT_MS
        },
@@ -2518,6 +2518,17 @@ static struct config_int ConfigureNamesInt[] =
        NULL, NULL, NULL
    },
 
+   {
+       {"idle_session_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+           gettext_noop("Sets the maximum allowed idle time between queries, when not in a transaction."),
+           gettext_noop("A value of 0 turns off the timeout."),
+           GUC_UNIT_MS
+       },
+       &IdleSessionTimeout,
+       0, 0, INT_MAX,
+       NULL, NULL, NULL
+   },
+
    {
        {"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
            gettext_noop("Minimum age at which VACUUM should freeze a table row."),
index 5298e18ecd5589919969d81e529ed4aa86b00e8d..033aa335a012b0cf85c83bd94833164bc7f607da 100644 (file)
 #statement_timeout = 0         # in milliseconds, 0 is disabled
 #lock_timeout = 0          # in milliseconds, 0 is disabled
 #idle_in_transaction_session_timeout = 0   # in milliseconds, 0 is disabled
+#idle_session_timeout = 0      # in milliseconds, 0 is disabled
 #vacuum_freeze_min_age = 50000000
 #vacuum_freeze_table_age = 150000000
 #vacuum_multixact_freeze_min_age = 5000000
index 2c71db79c0af290b72638b455e4b18eefe0f7860..1bdc97e3082d7c59d8d4208a637d16df994c3dec 100644 (file)
@@ -82,6 +82,7 @@ extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
 extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
 extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
+extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
 
 extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
index 989c5849d45830193dadc75a72a0b1d1ff40a116..0786fcf103a708542f6b2dd5bf751725460c027d 100644 (file)
@@ -378,6 +378,7 @@ extern PGDLLIMPORT int DeadlockTimeout;
 extern PGDLLIMPORT int StatementTimeout;
 extern PGDLLIMPORT int LockTimeout;
 extern PGDLLIMPORT int IdleInTransactionSessionTimeout;
+extern PGDLLIMPORT int IdleSessionTimeout;
 extern bool log_lock_waits;
 
 
index 8adb4e14cacc1c4e9b3cfa160d5b7adecb37614f..ecb2a366a5f44783293eab2d79601a3f836c272f 100644 (file)
@@ -31,10 +31,11 @@ typedef enum TimeoutId
    STANDBY_TIMEOUT,
    STANDBY_LOCK_TIMEOUT,
    IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
+   IDLE_SESSION_TIMEOUT,
    /* First user-definable timeout reason */
    USER_TIMEOUT,
    /* Maximum number of timeout reasons */
-   MAX_TIMEOUTS = 16
+   MAX_TIMEOUTS = USER_TIMEOUT + 10
 } TimeoutId;
 
 /* callback function signature */