]> granicus.if.org Git - postgresql/commitdiff
Add support TCP user timeout in libpq and the backend server
authorMichael Paquier <michael@paquier.xyz>
Sat, 6 Apr 2019 06:23:37 +0000 (15:23 +0900)
committerMichael Paquier <michael@paquier.xyz>
Sat, 6 Apr 2019 06:23:37 +0000 (15:23 +0900)
Similarly to the set of parameters for keepalive, a connection parameter
for libpq is added as well as a backend GUC, called tcp_user_timeout.

Increasing the TCP user timeout is useful to allow a connection to
survive extended periods without end-to-end connection, and decreasing
it allows application to fail faster.  By default, the parameter is 0,
which makes the connection use the system default, and follows a logic
close to the keepalive parameters in its handling.  When connecting
through a Unix-socket domain, the parameters have no effect.

Author: Ryohei Nagaura
Reviewed-by: Fabien Coelho, Robert Haas, Kyotaro Horiguchi, Kirk
Jamison, Mikalai Keida, Takayuki Tsunakawa, Andrei Yahorau
Discussion: https://postgr.es/m/EDA4195584F5064680D8130B1CA91C45367328@G01JPEXMBYT04

contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/sql/postgres_fdw.sql
doc/src/sgml/config.sgml
doc/src/sgml/libpq.sgml
src/backend/libpq/pqcomm.c
src/backend/utils/misc/guc.c
src/backend/utils/misc/postgresql.conf.sample
src/include/libpq/libpq-be.h
src/include/utils/guc.h
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/libpq-int.h

index 8ee1ff5093836116ab8911b19a15499bb32eebdf..3ed52709eeab84a8e9d637cd3f06a5d392541f0a 100644 (file)
@@ -151,6 +151,7 @@ ALTER SERVER testserver1 OPTIONS (
        keepalives 'value',
        keepalives_idle 'value',
        keepalives_interval 'value',
+       tcp_user_timeout 'value',
        -- requiressl 'value',
        sslcompression 'value',
        sslmode 'value',
index c588c96727c07a93ce53f6742e59f1b72a132b5b..3bfcdabc784b8a1a1e4aea31817b67bb30f22ff1 100644 (file)
@@ -164,6 +164,7 @@ ALTER SERVER testserver1 OPTIONS (
        keepalives 'value',
        keepalives_idle 'value',
        keepalives_interval 'value',
+       tcp_user_timeout 'value',
        -- requiressl 'value',
        sslcompression 'value',
        sslmode 'value',
index bc1d0f7bfaee47680a830a8b70666de70ebfdce6..d2da1abe618007316b58501019c32d53c6d90fbe 100644 (file)
@@ -939,6 +939,31 @@ include_dir 'conf.d'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-tcp-user-timeout" xreflabel="tcp_user_timeout">
+      <term><varname>tcp_user_timeout</varname> (<type>integer</type>)
+      <indexterm>
+       <primary><varname>tcp_user_timeout</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Specifies the number of milliseconds that transmitted data may
+        remain unacknowledged before a connection is forcibly closed.
+        A value of 0 uses the system default.
+        This parameter is supported only on systems that support
+        <symbol>TCP_USER_TIMEOUT</symbol>; on other systems, it must be zero.
+        In sessions connected via a Unix-domain socket, this parameter is
+        ignored and always reads as zero. 
+       </para>
+       <note>
+        <para>
+         This parameter is not supported on Windows and on Linux version
+         2.6.36 or older.
+        </para>
+       </note>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
      </sect2>
 
index 924b7ce50e19d61111f6cf055368e64b5a257e64..fe833aa62604d12c64a36143783b89ae4e55bc15 100644 (file)
@@ -1249,6 +1249,20 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-tcp-user-timeout" xreflabel="libpq_tcp_user_timeout">
+      <term><literal>tcp_user_timeout</literal></term>
+      <listitem>
+       <para>
+        Controls the number of milliseconds that transmitted data may
+        remain unacknowledged before a connection is forcibly closed.
+        A value of zero uses the system default. This parameter is
+        ignored for connections made via a Unix-domain socket.
+        It is only supported on systems where <symbol>TCP_USER_TIMEOUT</symbol>
+        is available; on other systems, it has no effect.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-tty" xreflabel="tty">
       <term><literal>tty</literal></term>
       <listitem>
index c39617a430aa88ae3007d3aede6fd27f013b14b2..384887e70d92c23a2afa204b48f0a8d21177339d 100644 (file)
@@ -825,6 +825,7 @@ StreamConnection(pgsocket server_fd, Port *port)
                (void) pq_setkeepalivesidle(tcp_keepalives_idle, port);
                (void) pq_setkeepalivesinterval(tcp_keepalives_interval, port);
                (void) pq_setkeepalivescount(tcp_keepalives_count, port);
+               (void) pq_settcpusertimeout(tcp_user_timeout, port);
        }
 
        return STATUS_OK;
@@ -1926,3 +1927,75 @@ pq_setkeepalivescount(int count, Port *port)
 
        return STATUS_OK;
 }
+
+int
+pq_gettcpusertimeout(Port *port)
+{
+#ifdef TCP_USER_TIMEOUT
+       if (port == NULL || IS_AF_UNIX(port->laddr.addr.ss_family))
+               return 0;
+
+       if (port->tcp_user_timeout != 0)
+               return port->tcp_user_timeout;
+
+       if (port->default_tcp_user_timeout == 0)
+       {
+               ACCEPT_TYPE_ARG3 size = sizeof(port->default_tcp_user_timeout);
+
+               if (getsockopt(port->sock, IPPROTO_TCP, TCP_USER_TIMEOUT,
+                                          (char *) &port->default_tcp_user_timeout,
+                                          &size) < 0)
+               {
+                       elog(LOG, "getsockopt(%s) failed: %m", "TCP_USER_TIMEOUT");
+                       port->default_tcp_user_timeout = -1;    /* don't know */
+               }
+       }
+
+       return port->default_tcp_user_timeout;
+#else
+       return 0;
+#endif
+}
+
+int
+pq_settcpusertimeout(int timeout, Port *port)
+{
+       if (port == NULL || IS_AF_UNIX(port->laddr.addr.ss_family))
+               return STATUS_OK;
+
+#ifdef TCP_USER_TIMEOUT
+       if (timeout == port->tcp_user_timeout)
+               return STATUS_OK;
+
+       if (port->default_tcp_user_timeout <= 0)
+       {
+               if (pq_gettcpusertimeout(port) < 0)
+               {
+                       if (timeout == 0)
+                               return STATUS_OK;       /* default is set but unknown */
+                       else
+                               return STATUS_ERROR;
+               }
+       }
+
+       if (timeout == 0)
+               timeout = port->default_tcp_user_timeout;
+
+       if (setsockopt(port->sock, IPPROTO_TCP, TCP_USER_TIMEOUT,
+                                  (char *) &timeout, sizeof(timeout)) < 0)
+       {
+               elog(LOG, "setsockopt(%s) failed: %m", "TCP_USER_TIMEOUT");
+               return STATUS_ERROR;
+       }
+
+       port->tcp_user_timeout = timeout;
+#else
+       if (timeout != 0)
+       {
+               elog(LOG, "setsockopt(%s) not supported", "TCP_USER_TIMEOUT");
+               return STATUS_ERROR;
+       }
+#endif
+
+       return STATUS_OK;
+}
index 1766e46037f3a7888cea632b0db642db88e8babe..f7f726b5aecbd556a43bab59b94fead52cb3c9da 100644 (file)
@@ -182,9 +182,11 @@ static const char *show_archive_command(void);
 static void assign_tcp_keepalives_idle(int newval, void *extra);
 static void assign_tcp_keepalives_interval(int newval, void *extra);
 static void assign_tcp_keepalives_count(int newval, void *extra);
+static void assign_tcp_user_timeout(int newval, void *extra);
 static const char *show_tcp_keepalives_idle(void);
 static const char *show_tcp_keepalives_interval(void);
 static const char *show_tcp_keepalives_count(void);
+static const char *show_tcp_user_timeout(void);
 static bool check_maxconnections(int *newval, void **extra, GucSource source);
 static bool check_max_worker_processes(int *newval, void **extra, GucSource source);
 static bool check_autovacuum_max_workers(int *newval, void **extra, GucSource source);
@@ -530,6 +532,7 @@ char           *application_name;
 int                    tcp_keepalives_idle;
 int                    tcp_keepalives_interval;
 int                    tcp_keepalives_count;
+int                    tcp_user_timeout;
 
 /*
  * SSL renegotiation was been removed in PostgreSQL 9.5, but we tolerate it
@@ -3182,6 +3185,17 @@ static struct config_int ConfigureNamesInt[] =
                NULL, NULL, NULL
        },
 
+       {
+               {"tcp_user_timeout", PGC_USERSET, CLIENT_CONN_OTHER,
+                       gettext_noop("TCP user timeout."),
+                       gettext_noop("A value of 0 uses the system default."),
+                       GUC_UNIT_MS
+               },
+               &tcp_user_timeout,
+               0, 0, INT_MAX,
+               NULL, assign_tcp_user_timeout, show_tcp_user_timeout
+       },
+
        /* End-of-list marker */
        {
                {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
@@ -11238,6 +11252,23 @@ show_tcp_keepalives_count(void)
        return nbuf;
 }
 
+static void
+assign_tcp_user_timeout(int newval, void *extra)
+{
+       /* See comments in assign_tcp_keepalives_idle */
+       (void) pq_settcpusertimeout(newval, MyProcPort);
+}
+
+static const char *
+show_tcp_user_timeout(void)
+{
+       /* See comments in assign_tcp_keepalives_idle */
+       static char nbuf[16];
+
+       snprintf(nbuf, sizeof(nbuf), "%d", pq_gettcpusertimeout(MyProcPort));
+       return nbuf;
+}
+
 static bool
 check_maxconnections(int *newval, void **extra, GucSource source)
 {
index bbbeb4bb155db2c518890fec493fec71740f5abd..77bb7c24028001ce6927e01a59f37bd1224385de 100644 (file)
@@ -73,7 +73,7 @@
 #bonjour_name = ''                     # defaults to the computer name
                                        # (change requires restart)
 
-# - TCP Keepalives -
+# - TCP settings -
 # see "man 7 tcp" for details
 
 #tcp_keepalives_idle = 0               # TCP_KEEPIDLE, in seconds;
@@ -82,6 +82,8 @@
                                        # 0 selects the system default
 #tcp_keepalives_count = 0              # TCP_KEEPCNT;
                                        # 0 selects the system default
+#tcp_user_timeout = 0                  # TCP_USER_TIMEOUT, in milliseconds;
+                                       # 0 selects the system default
 
 # - Authentication -
 
index b135ef9d9fd520b3203f15c09d9e89bd34e50b46..00b1fbe44158dbed6c281701474572edefc9a269 100644 (file)
@@ -155,7 +155,7 @@ typedef struct Port
        HbaLine    *hba;
 
        /*
-        * TCP keepalive settings.
+        * TCP keepalive and user timeout settings.
         *
         * default values are 0 if AF_UNIX or not yet known; current values are 0
         * if AF_UNIX or using the default. Also, -1 in a default value means we
@@ -164,9 +164,11 @@ typedef struct Port
        int                     default_keepalives_idle;
        int                     default_keepalives_interval;
        int                     default_keepalives_count;
+       int                     default_tcp_user_timeout;
        int                     keepalives_idle;
        int                     keepalives_interval;
        int                     keepalives_count;
+       int                     tcp_user_timeout;
 
        /*
         * GSSAPI structures.
@@ -306,9 +308,11 @@ extern ProtocolVersion FrontendProtocol;
 extern int     pq_getkeepalivesidle(Port *port);
 extern int     pq_getkeepalivesinterval(Port *port);
 extern int     pq_getkeepalivescount(Port *port);
+extern int     pq_gettcpusertimeout(Port *port);
 
 extern int     pq_setkeepalivesidle(int idle, Port *port);
 extern int     pq_setkeepalivesinterval(int interval, Port *port);
 extern int     pq_setkeepalivescount(int count, Port *port);
+extern int     pq_settcpusertimeout(int timeout, Port *port);
 
 #endif                                                 /* LIBPQ_BE_H */
index 2f7cf919335c93b6bbda2452360a84dbd79cfff9..6c41edac008a7c53e8793b24dfdc8d0907f61651 100644 (file)
@@ -271,6 +271,7 @@ extern PGDLLIMPORT char *application_name;
 extern int     tcp_keepalives_idle;
 extern int     tcp_keepalives_interval;
 extern int     tcp_keepalives_count;
+extern int     tcp_user_timeout;
 
 #ifdef TRACE_SORT
 extern bool trace_sort;
index 5dcc7d76922cea86ed82c5df9239a9f92cac84af..87df79880afc94013fb17d3235231887ac721183 100644 (file)
@@ -270,6 +270,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
                "TCP-Keepalives-Count", "", 10, /* strlen(INT32_MAX) == 10 */
        offsetof(struct pg_conn, keepalives_count)},
 
+       {"tcp_user_timeout", NULL, NULL, NULL,
+               "TCP-User-Timeout", "", 10, /* strlen(INT32_MAX) == 10 */
+       offsetof(struct pg_conn, pgtcp_user_timeout)},
+
        /*
         * ssl options are allowed even without client SSL support because the
         * client can still handle SSL modes "disable" and "allow". Other
@@ -1833,6 +1837,41 @@ setKeepalivesWin32(PGconn *conn)
 #endif                                                 /* SIO_KEEPALIVE_VALS */
 #endif                                                 /* WIN32 */
 
+/*
+ * Set the TCP user timeout.
+ */
+static int
+setTCPUserTimeout(PGconn *conn)
+{
+       int                     timeout;
+
+       if (conn->pgtcp_user_timeout == NULL)
+               return 1;
+
+       if (!parse_int_param(conn->pgtcp_user_timeout, &timeout, conn,
+                                                "tcp_user_timeout"))
+               return 0;
+
+       if (timeout < 0)
+               timeout = 0;
+
+#ifdef TCP_USER_TIMEOUT
+       if (setsockopt(conn->sock, IPPROTO_TCP, TCP_USER_TIMEOUT,
+                                  (char *) &timeout, sizeof(timeout)) < 0)
+       {
+               char            sebuf[256];
+
+               appendPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("setsockopt(%s) failed: %s\n"),
+                                                 "TCP_USER_TIMEOUT",
+                                                 SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+               return 0;
+       }
+#endif
+
+       return 1;
+}
+
 /* ----------
  * connectDBStart -
  *             Begin the process of making a connection to the backend.
@@ -2480,6 +2519,8 @@ keep_going:                                               /* We will come back to here until there is
                                                        err = 1;
 #endif                                                 /* SIO_KEEPALIVE_VALS */
 #endif                                                 /* WIN32 */
+                                               else if (!setTCPUserTimeout(conn))
+                                                       err = 1;
 
                                                if (err)
                                                {
@@ -3863,6 +3904,8 @@ freePGconn(PGconn *conn)
                free(conn->pgtty);
        if (conn->connect_timeout)
                free(conn->connect_timeout);
+       if (conn->pgtcp_user_timeout)
+               free(conn->pgtcp_user_timeout);
        if (conn->pgoptions)
                free(conn->pgoptions);
        if (conn->appname)
index 84222f2c7ca0082818bcd30ac2c8d2f1a8c1371e..1221ea9eefa2aa4afdc2ba674588c7963eb6d191 100644 (file)
@@ -336,6 +336,7 @@ struct pg_conn
        char       *pgtty;                      /* tty on which the backend messages is
                                                                 * displayed (OBSOLETE, NOT USED) */
        char       *connect_timeout;    /* connection timeout (numeric string) */
+       char       *pgtcp_user_timeout; /* tcp user timeout (numeric string) */
        char       *client_encoding_initial;    /* encoding to use */
        char       *pgoptions;          /* options to start the backend with */
        char       *appname;            /* application name */